/*-------------------------------------------------------------------------
 *
 * fe-connect.c
 *	  与设置连接到后端相关的函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-connect.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <unistd.h>

#include "common/ip.h"
#include "common/link-canary.h"
#include "common/scram-common.h"
#include "common/string.h"
#include "fe-auth.h"
#include "libpq-fe.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
#include "pg_config_paths.h"
#include "port/pg_bswap.h"

#ifdef WIN32
#include "win32.h"
#ifdef _WIN32_IE
#undef _WIN32_IE
#endif
#define _WIN32_IE 0x0500
#ifdef near
#undef near
#endif
#define near
#include <shlobj.h>
#ifdef _MSC_VER					/* mstcpip.h 在 mingw 上缺失 */
#include <mstcpip.h>
#endif
#else
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#endif

#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
#include "pthread-win32.h"
#else
#include <pthread.h>
#endif
#endif

#ifdef USE_LDAP
#ifdef WIN32
#include <winldap.h>
#else
/* OpenLDAP 弃用 RFC 1823，但我们希望遵循标准 */
#define LDAP_DEPRECATED 1
#include <ldap.h>
typedef struct timeval LDAP_TIMEVAL;
#endif
static int	fc_ldapServiceLookup(const char *fc_purl, PQconninfoOption *fc_options,
							  PQExpBuffer fc_errorMessage);
#endif

#ifndef WIN32
#define PGPASSFILE ".pgpass"
#else
#define PGPASSFILE "pgpass.conf"
#endif

/*
 * 询问设置应用程序名称在启动数据包中的情况下，9.0 之前的服务器将返回此 SQLSTATE。
 * 我们硬编码该值，而不是查看 errcodes.h，因为它反映的是历史行为
 * 而不是当前代码的行为。
 */
#define ERRCODE_APPNAME_UNKNOWN "42704"

/* 这是协议的一部分，因此只需定义它 */
#define ERRCODE_INVALID_PASSWORD "28P01"
/* 这也是 */
#define ERRCODE_CANNOT_CONNECT_NOW "57P03"

/*
 * 处理各种平台特定的 TCP keepalive 套接字
 * 选项的拼写方式。这不包括 Windows，Windows 通常会独自处理。
 */
#if defined(TCP_KEEPIDLE)
/* TCP_KEEPIDLE 是此选项在 Linux 和 *BSD 上的名称 */
#define PG_TCP_KEEPALIVE_IDLE TCP_KEEPIDLE
#define PG_TCP_KEEPALIVE_IDLE_STR "TCP_KEEPIDLE"
#elif defined(TCP_KEEPALIVE_THRESHOLD)
/* TCP_KEEPALIVE_THRESHOLD 是此选项在 Solaris >= 11 上的名称 */
#define PG_TCP_KEEPALIVE_IDLE TCP_KEEPALIVE_THRESHOLD
#define PG_TCP_KEEPALIVE_IDLE_STR "TCP_KEEPALIVE_THRESHOLD"
#elif defined(TCP_KEEPALIVE) && defined(__darwin__)
/* TCP_KEEPALIVE 是此选项在 macOS 上的名称 */
/* 注意：Solaris 有此符号，但它的含义不同 */
#define PG_TCP_KEEPALIVE_IDLE TCP_KEEPALIVE
#define PG_TCP_KEEPALIVE_IDLE_STR "TCP_KEEPALIVE"
#endif

/*
 * 如果没有通过参数指定或通过环境变量定义，则使用备用选项
 */
#define DefaultHost		"localhost"
#define DefaultOption	""
#ifdef USE_SSL
#define DefaultChannelBinding	"prefer"
#else
#define DefaultChannelBinding	"disable"
#endif
#define DefaultTargetSessionAttrs	"any"
#ifdef USE_SSL
#define DefaultSSLMode "prefer"
#else
#define DefaultSSLMode	"disable"
#endif
#ifdef ENABLE_GSS
#include "fe-gssapi-common.h"
#define DefaultGSSMode "prefer"
#else
#define DefaultGSSMode "disable"
#endif

/* ----------
 * conninfo 参数及其备用资源的定义。
 *
 * 如果环境变量和编译时定义被指定为 NULL，则没有
 * 备用。 如果最后仍无法确定某个选项的值，则返回错误。
 *
 * 在 conninfo_add_defaults 中，用户名的值被特别处理。
 * 如果没有通过其他方式获取该值，则用户名由 pg_fe_getauthname() 确定。
 *
 * Label 和 Disp-Char 条目用于希望使用 PQconndefaults()
 * 创建通用数据库连接对话框的应用程序。 Disp-Char 定义如下：
 *		""		正常输入字段
 *		"*"		密码字段 - 隐藏值
 *		"D"		调试选项 - 默认不显示
 *
 * PQconninfoOptions[] 是一个常量静态数组，我们用它来初始化
 * 动态分配的工作副本。 PQconninfoOptions[] 中的所有 "val" 字段
 * *必须* 为 NULL。在工作副本中，非 NULL 的 "val" 字段指向 malloc 的字符串，
 * 在工作数组被释放时应释放这些字符串（见 PQconninfoFree）。
 *
 * 每个结构的第一部分与 libpq-fe.h 中的部分相同，
 * 这是必需的，因为我们在两者之间使用 memcpy() 复制数据！
 * ----------
 */
typedef struct _internalPQconninfoOption
{
	char	   *keyword;		/* 选项的关键字 */
	char	   *envvar;			/* 回退环境变量名称 */
	char	   *compiled;		/* 回退编译的默认值 */
	char	   *val;			/* 选项的当前值，或 NULL		*/
	char	   *label;			/* 连接对话框中字段的标签 */
	char	   *dispchar;		/* 指示如何在连接对话框中显示该字段。
 * 值为："" 原样显示输入值 "*" 密码字段 - 隐藏值 "D" 调试选项 - 默认不显示 */
	int			dispsize;		/* 对话框中文本的字符大小 */
	/* ---
	 * 在此注释之上，任何内容都必须与
	 * libpq-fe.h 中的 PQconninfoOption 同步，因为我们在
	 * 它们之间使用 memcpy() 复制数据！
	 * ---
	 */
	off_t		connofs;		/* 在 PGconn 结构中的偏移量，如果不存在则为 -1 */
} internalPQconninfoOption;

static const internalPQconninfoOption PQconninfoOptions[] = {
	{"service", "PGSERVICE", NULL, NULL,
	"Database-Service", "", 20, -1},

	{"user", "PGUSER", NULL, NULL,
		"Database-User", "", 20,
	offsetof(struct pg_conn, pguser)},

	{"password", "PGPASSWORD", NULL, NULL,
		"Database-Password", "*", 20,
	offsetof(struct pg_conn, pgpass)},

	{"passfile", "PGPASSFILE", NULL, NULL,
		"Database-Password-File", "", 64,
	offsetof(struct pg_conn, pgpassfile)},

	{"channel_binding", "PGCHANNELBINDING", DefaultChannelBinding, NULL,
		"Channel-Binding", "", 8,	/* sizeof("require") == 8 */
	offsetof(struct pg_conn, channel_binding)},

	{"connect_timeout", "PGCONNECT_TIMEOUT", NULL, NULL,
		"Connect-timeout", "", 10,	/* strlen(INT32_MAX) == 10 */
	offsetof(struct pg_conn, connect_timeout)},

	{"dbname", "PGDATABASE", NULL, NULL,
		"Database-Name", "", 20,
	offsetof(struct pg_conn, dbName)},

	{"host", "PGHOST", NULL, NULL,
		"Database-Host", "", 40,
	offsetof(struct pg_conn, pghost)},

	{"hostaddr", "PGHOSTADDR", NULL, NULL,
		"Database-Host-IP-Address", "", 45,
	offsetof(struct pg_conn, pghostaddr)},

	{"port", "PGPORT", DEF_PGPORT_STR, NULL,
		"Database-Port", "", 6,
	offsetof(struct pg_conn, pgport)},

	{"client_encoding", "PGCLIENTENCODING", NULL, NULL,
		"Client-Encoding", "", 10,
	offsetof(struct pg_conn, client_encoding_initial)},

	{"options", "PGOPTIONS", DefaultOption, NULL,
		"Backend-Options", "", 40,
	offsetof(struct pg_conn, pgoptions)},

	{"application_name", "PGAPPNAME", NULL, NULL,
		"Application-Name", "", 64,
	offsetof(struct pg_conn, appname)},

	{"fallback_application_name", NULL, NULL, NULL,
		"Fallback-Application-Name", "", 64,
	offsetof(struct pg_conn, fbappname)},

	{"keepalives", NULL, NULL, NULL,
		"TCP-Keepalives", "", 1,	/* 应该仅为 '0' 或 '1' */
	offsetof(struct pg_conn, keepalives)},

	{"keepalives_idle", NULL, NULL, NULL,
		"TCP-Keepalives-Idle", "", 10,	/* strlen(INT32_MAX) == 10 */
	offsetof(struct pg_conn, keepalives_idle)},

	{"keepalives_interval", NULL, NULL, NULL,
		"TCP-Keepalives-Interval", "", 10,	/* strlen(INT32_MAX) == 10 */
	offsetof(struct pg_conn, keepalives_interval)},

	{"keepalives_count", NULL, NULL, NULL,
		"TCP-Keepalives-Count", "", 10, /* strlen(INT32_MAX) == 10 */
	offsetof(struct pg_conn, keepalives_count)},

	{"tcp_user_timeout", NULL, NULL, NULL,
		"TCP-User-Timeout", "", 10, /* strlen(INT32_MAX) == 10 */
	offsetof(struct pg_conn, pgtcp_user_timeout)},

	/*
	 * ssl 选项即使在没有客户端 SSL 支持的情况下也是允许的，因为
	 * 客户端仍然可以处理 SSL 模式 "disable" 和 "allow"。其他
	 * 参数对非 SSL 连接没有影响，因此没有理由将它们排除，
	 * 因为它们都不是必需的。
	 */
	{"sslmode", "PGSSLMODE", DefaultSSLMode, NULL,
		"SSL-Mode", "", 12,		/* sizeof("verify-full") == 12 */
	offsetof(struct pg_conn, sslmode)},

	{"sslcompression", "PGSSLCOMPRESSION", "0", NULL,
		"SSL-Compression", "", 1,
	offsetof(struct pg_conn, sslcompression)},

	{"sslcert", "PGSSLCERT", NULL, NULL,
		"SSL-Client-Cert", "", 64,
	offsetof(struct pg_conn, sslcert)},

	{"sslkey", "PGSSLKEY", NULL, NULL,
		"SSL-Client-Key", "", 64,
	offsetof(struct pg_conn, sslkey)},

#ifdef FDDGM
	{"sslenccert", "PGSSLENCCERT", NULL, NULL,
		"SSL-Encryption-Client-Cert", "", 64,
	offsetof(struct pg_conn, sslenccert)},

	{"sslenckey", "PGSSLENCKEY", NULL, NULL,
		"SSL-Encryption-Client-Key", "", 64,
	offsetof(struct pg_conn, sslenckey)},
#endif

	{"sslpassword", NULL, NULL, NULL,
		"SSL-Client-Key-Password", "*", 20,
	offsetof(struct pg_conn, sslpassword)},

	{"sslrootcert", "PGSSLROOTCERT", NULL, NULL,
		"SSL-Root-Certificate", "", 64,
	offsetof(struct pg_conn, sslrootcert)},

	{"sslcrl", "PGSSLCRL", NULL, NULL,
		"SSL-Revocation-List", "", 64,
	offsetof(struct pg_conn, sslcrl)},

	{"sslcrldir", "PGSSLCRLDIR", NULL, NULL,
		"SSL-Revocation-List-Dir", "", 64,
	offsetof(struct pg_conn, sslcrldir)},

	{"sslsni", "PGSSLSNI", "1", NULL,
		"SSL-SNI", "", 1,
	offsetof(struct pg_conn, sslsni)},

	{"requirepeer", "PGREQUIREPEER", NULL, NULL,
		"Require-Peer", "", 10,
	offsetof(struct pg_conn, requirepeer)},

	{"ssl_min_protocol_version", "PGSSLMINPROTOCOLVERSION", "TLSv1.2", NULL,
		"SSL-Minimum-Protocol-Version", "", 8,	/* sizeof("TLSv1.x") == 8 */
	offsetof(struct pg_conn, ssl_min_protocol_version)},

	{"ssl_max_protocol_version", "PGSSLMAXPROTOCOLVERSION", NULL, NULL,
		"SSL-Maximum-Protocol-Version", "", 8,	/* sizeof("TLSv1.x") == 8 */
	offsetof(struct pg_conn, ssl_max_protocol_version)},

	/*
	 * 与 SSL 一样，所有 GSS 选项即使在没有支持的构建中也会暴露。
	 */
	{"gssencmode", "PGGSSENCMODE", DefaultGSSMode, NULL,
		"GSSENC-Mode", "", 8,	/* sizeof("disable") == 8 */
	offsetof(struct pg_conn, gssencmode)},

	/* Kerberos 和 GSSAPI 认证支持指定服务名称 */
	{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
		"Kerberos-service-name", "", 20,
	offsetof(struct pg_conn, krbsrvname)},

	{"gsslib", "PGGSSLIB", NULL, NULL,
		"GSS-library", "", 7,	/* sizeof("gssapi") == 7 */
	offsetof(struct pg_conn, gsslib)},

	{"replication", NULL, NULL, NULL,
		"Replication", "D", 5,
	offsetof(struct pg_conn, replication)},

	{"target_session_attrs", "PGTARGETSESSIONATTRS",
		DefaultTargetSessionAttrs, NULL,
		"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
	offsetof(struct pg_conn, target_session_attrs)},

	/* 终止条目 --- 必须是最后一个 */
	{NULL, NULL, NULL, NULL,
	NULL, NULL, 0}
};

static const PQEnvironmentOption EnvironmentOptions[] =
{
	/* 通用用户界面设置 */
	{
		"PGDATESTYLE", "datestyle"
	},
	{
		"PGTZ", "timezone"
	},
	/* 内部性能相关设置 */
	{
		"PGGEQO", "geqo"
	},
	{
		NULL, NULL
	}
};

/* 连接 URI 必须以以下任一标识符开头： */
static const char uri_designator[] = "postgresql://";
static const char short_uri_designator[] = "postgres://";

static bool fc_connectOptions1(PGconn *fc_conn, const char *fc_conninfo);
static bool fc_connectOptions2(PGconn *fc_conn);
static int	fc_connectDBStart(PGconn *fc_conn);
static int	fc_connectDBComplete(PGconn *fc_conn);
static PGPing fc_internal_ping(PGconn *fc_conn);
static PGconn *fc_makeEmptyPGconn(void);
static void fc_pqFreeCommandQueue(PGcmdQueueEntry *fc_queue);
static bool fc_fillPGconn(PGconn *fc_conn, PQconninfoOption *fc_connOptions);
static void fc_freePGconn(PGconn *fc_conn);
static void fc_closePGconn(PGconn *fc_conn);
static void fc_release_conn_addrinfo(PGconn *fc_conn);
static void fc_sendTerminateConn(PGconn *fc_conn);
static PQconninfoOption *fc_conninfo_init(PQExpBuffer fc_errorMessage);
static PQconninfoOption *fc_parse_connection_string(const char *fc_conninfo,
												 PQExpBuffer fc_errorMessage, bool fc_use_defaults);
static int	fc_uri_prefix_length(const char *fc_connstr);
static bool fc_recognized_connection_string(const char *fc_connstr);
static PQconninfoOption *fc_conninfo_parse(const char *fc_conninfo,
										PQExpBuffer fc_errorMessage, bool fc_use_defaults);
static PQconninfoOption *fc_conninfo_array_parse(const char *const *fc_keywords,
											  const char *const *fc_values, PQExpBuffer fc_errorMessage,
											  bool fc_use_defaults, int fc_expand_dbname);
static bool fc_conninfo_add_defaults(PQconninfoOption *fc_options,
								  PQExpBuffer fc_errorMessage);
static PQconninfoOption *fc_conninfo_uri_parse(const char *fc_uri,
											PQExpBuffer fc_errorMessage, bool fc_use_defaults);
static bool fc_conninfo_uri_parse_options(PQconninfoOption *fc_options,
									   const char *fc_uri, PQExpBuffer fc_errorMessage);
static bool fc_conninfo_uri_parse_params(char *fc_params,
									  PQconninfoOption *fc_connOptions,
									  PQExpBuffer fc_errorMessage);
static char *fc_conninfo_uri_decode(const char *fc_str, PQExpBuffer fc_errorMessage);
static bool fc_get_hexdigit(char fc_digit, int *fc_value);
static const char *fc_conninfo_getval(PQconninfoOption *fc_connOptions,
								   const char *fc_keyword);
static PQconninfoOption *fc_conninfo_storeval(PQconninfoOption *fc_connOptions,
										   const char *fc_keyword, const char *fc_value,
										   PQExpBuffer fc_errorMessage, bool fc_ignoreMissing, bool fc_uri_decode);
static PQconninfoOption *fc_conninfo_find(PQconninfoOption *fc_connOptions,
									   const char *fc_keyword);
static void fc_defaultNoticeReceiver(void *fc_arg, const PGresult *fc_res);
static void fc_defaultNoticeProcessor(void *fc_arg, const char *fc_message);
static int	fc_parseServiceInfo(PQconninfoOption *fc_options,
							 PQExpBuffer fc_errorMessage);
static int	fc_parseServiceFile(const char *fc_serviceFile,
							 const char *fc_service,
							 PQconninfoOption *fc_options,
							 PQExpBuffer fc_errorMessage,
							 bool *fc_group_found);
static char *fc_pwdfMatchesString(char *fc_buf, const char *fc_token);
static char *fc_passwordFromFile(const char *fc_hostname, const char *fc_port, const char *fc_dbname,
							  const char *fc_username, const char *fc_pgpassfile);
static void fc_pgpassfileWarning(PGconn *fc_conn);
static void fc_default_threadlock(int fc_acquire);
static bool fc_sslVerifyProtocolVersion(const char *fc_version);
static bool fc_sslVerifyProtocolRange(const char *fc_min, const char *fc_max);
static bool fc_parse_int_param(const char *fc_value, int *fc_result, PGconn *fc_conn,
							const char *fc_context);


/* 全局变量，因为 fe-auth.c 需要访问它 */
pgthreadlock_t pg_g_threadlock = fc_default_threadlock;


/*
 *		pqDropConnection
 *
 * 关闭与服务器的任何物理连接，并重置连接对象内部的相关状态。
 * 但是，我们不会释放重新连接所需的状态，也不会释放可能仍然
 * 有用的本地状态。
 *
 * 我们可以始终刷新输出缓冲区，因为不再有希望发送该数据。
 * 但是，未处理的输入数据可能仍然有价值，因此调用者必须告知
 * 我们是否刷新该数据。
 */
void pqDropConnection(PGconn *fc_conn, bool fc_flushInput)
{
	/* 丢弃任何 SSL 状态 */
	pqsecure_close(fc_conn);

	/* 关闭套接字本身 */
	if (fc_conn->sock != PGINVALID_SOCKET)
		closesocket(fc_conn->sock);
	fc_conn->sock = PGINVALID_SOCKET;

	/* 可选择性丢弃任何未读数据 */
	if (fc_flushInput)
		fc_conn->inStart = fc_conn->inCursor = fc_conn->inEnd = 0;

	/* 始终丢弃任何未发送的数据 */
	fc_conn->outCount = 0;

	/* 同样，丢弃任何挂起的流水线命令 */
	fc_pqFreeCommandQueue(fc_conn->cmd_queue_head);
	fc_conn->cmd_queue_head = fc_conn->cmd_queue_tail = NULL;
	fc_pqFreeCommandQueue(fc_conn->cmd_queue_recycle);
	fc_conn->cmd_queue_recycle = NULL;

	/* 释放身份验证/加密状态 */
#ifdef ENABLE_GSS
	{
		OM_uint32	fc_min_s;

		if (fc_conn->gcred != GSS_C_NO_CREDENTIAL)
		{
			gss_release_cred(&fc_min_s, &fc_conn->gcred);
			fc_conn->gcred = GSS_C_NO_CREDENTIAL;
		}
		if (fc_conn->gctx)
			gss_delete_sec_context(&fc_min_s, &fc_conn->gctx, GSS_C_NO_BUFFER);
		if (fc_conn->gtarg_nam)
			gss_release_name(&fc_min_s, &fc_conn->gtarg_nam);
		if (fc_conn->gss_SendBuffer)
		{
			free(fc_conn->gss_SendBuffer);
			fc_conn->gss_SendBuffer = NULL;
		}
		if (fc_conn->gss_RecvBuffer)
		{
			free(fc_conn->gss_RecvBuffer);
			fc_conn->gss_RecvBuffer = NULL;
		}
		if (fc_conn->gss_ResultBuffer)
		{
			free(fc_conn->gss_ResultBuffer);
			fc_conn->gss_ResultBuffer = NULL;
		}
		fc_conn->gssenc = false;
	}
#endif
#ifdef ENABLE_SSPI
	if (fc_conn->sspitarget)
	{
		free(fc_conn->sspitarget);
		fc_conn->sspitarget = NULL;
	}
	if (fc_conn->sspicred)
	{
		FreeCredentialsHandle(fc_conn->sspicred);
		free(fc_conn->sspicred);
		fc_conn->sspicred = NULL;
	}
	if (fc_conn->sspictx)
	{
		DeleteSecurityContext(fc_conn->sspictx);
		free(fc_conn->sspictx);
		fc_conn->sspictx = NULL;
	}
	fc_conn->usesspi = 0;
#endif
	if (fc_conn->sasl_state)
	{
		fc_conn->sasl->free(fc_conn->sasl_state);
		fc_conn->sasl_state = NULL;
	}
}

/*
 * pqFreeCommandQueue
 * 释放传入的 PGcmdQueueEntry 队列的所有条目。
 */
static void fc_pqFreeCommandQueue(PGcmdQueueEntry *fc_queue)
{
	while (fc_queue != NULL)
	{
		PGcmdQueueEntry *fc_cur = fc_queue;

		fc_queue = fc_cur->next;
		if (fc_cur->query)
			free(fc_cur->query);
		free(fc_cur);
	}
}

/*
 *		pqDropServerData
 *
 * 清除从服务器接收（或推导出）的所有连接状态数据。
 * 在连接不同服务器的尝试之间这样做是必要的，否则我们可能会错误地保留
 * 一些来自旧服务器的数据。
 *
 * 将此合并到 pqDropConnection 中可能更好，但
 * 现在我们不能这样做，因为该函数在检测到连接丢失后立即调用
 * （例如，参考 pqReadData）。在我们实际开始新的连接之前，
 * 这些数据应该被保留。
 */
static void fc_pqDropServerData(PGconn *fc_conn)
{
	PGnotify   *fc_notify;
	pgParameterStatus *fc_pstatus;

	/* 忘记挂起的通知 */
	fc_notify = fc_conn->notifyHead;
	while (fc_notify != NULL)
	{
		PGnotify   *fc_prev = fc_notify;

		fc_notify = fc_notify->next;
		free(fc_prev);
	}
	fc_conn->notifyHead = fc_conn->notifyTail = NULL;

	/* 重置 ParameterStatus 数据，以及从中推导出的变量 */
	fc_pstatus = fc_conn->pstatus;
	while (fc_pstatus != NULL)
	{
		pgParameterStatus *fc_prev = fc_pstatus;

		fc_pstatus = fc_pstatus->next;
		free(fc_prev);
	}
	fc_conn->pstatus = NULL;
	fc_conn->client_encoding = PG_SQL_ASCII;
	fc_conn->std_strings = false;
	fc_conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
	fc_conn->in_hot_standby = PG_BOOL_UNKNOWN;
	fc_conn->sversion = 0;

	/* 丢弃大对象查找数据 */
	if (fc_conn->lobjfuncs)
		free(fc_conn->lobjfuncs);
	fc_conn->lobjfuncs = NULL;

	/* 重置各种其他每连接状态 */
	fc_conn->last_sqlstate[0] = '\0';
	fc_conn->auth_req_received = false;
	fc_conn->password_needed = false;
	fc_conn->write_failed = false;
	if (fc_conn->write_err_msg)
		free(fc_conn->write_err_msg);
	fc_conn->write_err_msg = NULL;
	fc_conn->be_pid = 0;
	fc_conn->be_key = 0;
}


/*
 *		连接到数据库
 *
 * 现在，使用此API的用户可以通过六种不同的方式连接到数据库。其中两种由于在将选项传递给后端时缺乏可扩展性，不推荐在新代码中使用。这两种是PQsetdb和PQsetdbLogin（前者现在是后者的宏）。
 *
 * 如果希望以同步（阻塞）方式连接，请使用函数PQconnectdb或PQconnectdbParams。前者接受必须解析的选项=值对字符串（或URI）；后者则接受两个以NULL结尾的数组。
 *
 * 要以异步（非阻塞）方式连接，请使用函数PQconnectStart或PQconnectStartParams（两者的差别与PQconnectdb和PQconnectdbParams相同）以及PQconnectPoll。
 *
 * 在内部，静态函数connectDBStart、connectDBComplete是连接过程的一部分。
 */

/*
 *		PQconnectdbParams
 *
 * 通过postmaster使用两个数组中的连接信息建立与postgres后端的连接。
 *
 * 关键字数组定义为
 *
 *	   const char *params[] = {"option1", "option2", NULL}
 *
 * 值数组定义为
 *
 *	   const char *values[] = {"value1", "value2", NULL}
 *
 * 返回一个PGconn*，该指针在所有后续libpq调用中都是必需的，或者在内存分配失败时返回NULL。
 * 如果返回的连接状态字段为CONNECTION_BAD，则一些字段可能会被置为NULL，而不是具有有效值。
 *
 * 无论此调用是否成功，您都应该调用PQfinish（如果conn不为NULL）。
 */
PGconn * PQconnectdbParams(const char *const *fc_keywords,
				  const char *const *fc_values,
				  int fc_expand_dbname)
{
	PGconn	   *fc_conn = PQconnectStartParams(fc_keywords, fc_values, fc_expand_dbname);

	if (fc_conn && fc_conn->status != CONNECTION_BAD)
		(void) fc_connectDBComplete(fc_conn);

	return fc_conn;
}

/*
 *		PQpingParams
 *
 * 检查服务器状态，接受与PQconnectdbParams相同的参数
 */
PGPing
PQpingParams(const char *const *fc_keywords,
			 const char *const *fc_values,
			 int fc_expand_dbname)
{
	PGconn	   *fc_conn = PQconnectStartParams(fc_keywords, fc_values, fc_expand_dbname);
	PGPing		fc_ret;

	fc_ret = fc_internal_ping(fc_conn);
	PQfinish(fc_conn);

	return fc_ret;
}

/*
 *		PQconnectdb
 *
 * 通过postmaster使用字符串中的连接信息建立与postgres后端的连接。
 *
 * conninfo字符串可以是以空格分隔的列表
 *
 *	   option = value
 *
 * 定义或URI（详细信息请参考文档）。值可能是一个不包含空格的单一值或一个单引号字符串。如果值的任何地方出现单引号，它必须用反斜杠转义，如\'
 *
 * 返回一个PGconn*，该指针在所有后续libpq调用中都是必需的，或者在内存分配失败时返回NULL。
 * 如果返回的连接状态字段为CONNECTION_BAD，则一些字段可能会被置为NULL，而不是具有有效值。
 *
 * 无论此调用是否成功，您都应该调用PQfinish（如果conn不为NULL）。
 */
PGconn * PQconnectdb(const char *fc_conninfo)
{
	PGconn	   *fc_conn = PQconnectStart(fc_conninfo);

	if (fc_conn && fc_conn->status != CONNECTION_BAD)
		(void) fc_connectDBComplete(fc_conn);

	return fc_conn;
}

/*
 *		PQping
 *
 * 检查服务器状态，接受与PQconnectdb相同的参数
 */
PGPing
PQping(const char *fc_conninfo)
{
	PGconn	   *fc_conn = PQconnectStart(fc_conninfo);
	PGPing		fc_ret;

	fc_ret = fc_internal_ping(fc_conn);
	PQfinish(fc_conn);

	return fc_ret;
}

/*
 *		PQconnectStartParams
 *
 * 开始通过后端的postmaster建立与postgres的连接，使用结构体中的连接信息。
 *
 * 有关字符串格式的定义，请参见PQconnectdbParams的注释。
 *
 * 返回一个PGconn*。如果返回NULL，则表示发生了malloc错误，您不应尝试继续此连接。如果返回的连接的状态字段为CONNECTION_BAD，则发生了错误。在这种情况下，您应该对结果调用PQfinish（可能首先检查错误消息）。如果发生这种情况，则结构的其他字段可能无效。如果状态字段不是CONNECTION_BAD，则此阶段已成功-调用PQconnectPoll，使用select(2)查看何时需要这样做。
 *
 * 有关更多信息，请参见PQconnectPoll。
 */
PGconn * PQconnectStartParams(const char *const *fc_keywords,
					 const char *const *fc_values,
					 int fc_expand_dbname)
{
	PGconn	   *fc_conn;
	PQconninfoOption *fc_connOptions;

	/*
	 * 为conn结构分配内存。请注意，我们还希望将conn->errorMessage初始化为空。所有 subsequent steps during
	 * 连接初始化将仅附加到该缓冲区。
	 */
	fc_conn = fc_makeEmptyPGconn();
	if (fc_conn == NULL)
		return NULL;

	/*
	 * 解析conninfo数组
	 */
	fc_connOptions = fc_conninfo_array_parse(fc_keywords, fc_values,
									   &fc_conn->errorMessage,
									   true, fc_expand_dbname);
	if (fc_connOptions == NULL)
	{
		fc_conn->status = CONNECTION_BAD;
		/* errorMessage已经设置 */
		return fc_conn;
	}

	/*
	 * 将选项值移动到conn结构中
	 */
	if (!fc_fillPGconn(fc_conn, fc_connOptions))
	{
		PQconninfoFree(fc_connOptions);
		return fc_conn;
	}

	/*
	 * 释放选项信息-现在所有信息都在conn中
	 */
	PQconninfoFree(fc_connOptions);

	/*
	 * 计算派生选项
	 */
	if (!fc_connectOptions2(fc_conn))
		return fc_conn;

	/*
	 * 连接到数据库
	 */
	if (!fc_connectDBStart(fc_conn))
	{
		/* 以防我们在connectDBStart中未能设置它 */
		fc_conn->status = CONNECTION_BAD;
	}

	return fc_conn;
}

/*
 *		PQconnectStart
 *
 * 开始通过后端的postmaster建立与postgres的连接，使用字符串中的连接信息。
 *
 * 有关字符串格式的定义，请参见PQconnectdb的注释。
 *
 * 返回一个PGconn*。如果返回NULL，则表示发生了malloc错误，您不应尝试继续此连接。如果返回的连接的状态字段为CONNECTION_BAD，则发生了错误。在这种情况下，您应该对结果调用PQfinish（可能首先检查错误消息）。如果发生这种情况，则结构的其他字段可能无效。如果状态字段不是CONNECTION_BAD，则此阶段已成功-调用PQconnectPoll，使用select(2)查看何时需要这样做。
 *
 * 有关更多信息，请参见PQconnectPoll。
 */
PGconn * PQconnectStart(const char *fc_conninfo)
{
	PGconn	   *fc_conn;

	/*
	 * 为conn结构分配内存。请注意，我们还希望将conn->errorMessage初始化为空。所有 subsequent steps during
	 * 连接初始化将仅附加到该缓冲区。
	 */
	fc_conn = fc_makeEmptyPGconn();
	if (fc_conn == NULL)
		return NULL;

	/*
	 * 解析conninfo字符串
	 */
	if (!fc_connectOptions1(fc_conn, fc_conninfo))
		return fc_conn;

	/*
	 * 计算派生选项
	 */
	if (!fc_connectOptions2(fc_conn))
		return fc_conn;

	/*
	 * 连接到数据库
	 */
	if (!fc_connectDBStart(fc_conn))
	{
		/* 以防我们在connectDBStart中未能设置它 */
		fc_conn->status = CONNECTION_BAD;
	}

	return fc_conn;
}

/*
 * 将选项值移动到conn结构中
 *
 * 不要在这里放任何花哨的东西 --- 智能应该在
 * connectOptions2 ...
 *
 * 成功时返回true。失败时返回false并设置错误消息。
 */
static bool fc_fillPGconn(PGconn *fc_conn, PQconninfoOption *fc_connOptions)
{
	const internalPQconninfoOption *fc_option;

	for (fc_option = PQconninfoOptions; fc_option->keyword; fc_option++)
	{
		if (fc_option->connofs >= 0)
		{
			const char *fc_tmp = fc_conninfo_getval(fc_connOptions, fc_option->keyword);

			if (fc_tmp)
			{
				char	  **fc_connmember = (char **) ((char *) fc_conn + fc_option->connofs);

				if (*fc_connmember)
					free(*fc_connmember);
				*fc_connmember = strdup(fc_tmp);
				if (*fc_connmember == NULL)
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("out of memory\n"));
					return false;
				}
			}
		}
	}

	return true;
}

/*
 *		connectOptions1
 *
 * 内部子例程，通过已创建的 PGconn 和 conninfo 字符串设置连接参数。派生设置应通过调用 connectOptions2 进行处理。 （我们将它们分开，因为 PQsetdbLogin 在其中覆盖了默认值。）
 *
 * 如果成功返回 true，遇到问题返回 false （在这种情况下会设置 errorMessage 和 conn->status）。
 */
static bool fc_connectOptions1(PGconn *fc_conn, const char *fc_conninfo)
{
	PQconninfoOption *fc_connOptions;

	/*
	 * 解析conninfo字符串
	 */
	fc_connOptions = fc_parse_connection_string(fc_conninfo, &fc_conn->errorMessage, true);
	if (fc_connOptions == NULL)
	{
		fc_conn->status = CONNECTION_BAD;
		/* errorMessage已经设置 */
		return false;
	}

	/*
	 * 将选项值移动到conn结构中
	 */
	if (!fc_fillPGconn(fc_conn, fc_connOptions))
	{
		fc_conn->status = CONNECTION_BAD;
		PQconninfoFree(fc_connOptions);
		return false;
	}

	/*
	 * 释放选项信息-现在所有信息都在conn中
	 */
	PQconninfoFree(fc_connOptions);

	return true;
}

/*
 * 计算简单逗号分隔列表中的元素数量。
 */
static int fc_count_comma_separated_elems(const char *fc_input)
{
	int			fc_n;

	fc_n = 1;
	for (; *fc_input != '\0'; fc_input++)
	{
		if (*fc_input == ',')
			fc_n++;
	}

	return fc_n;
}

/*
 * 解析简单的逗号分隔列表。
 *
 * 每次调用时，返回下一个元素的 malloc 副本，并设置 *more
 * 以指示列表中是否还有其他元素，并更新 *startptr 指向下一个元素（如果有）。
 *
 * 内存不足时，返回 NULL。
 */
static char * fc_parse_comma_separated_list(char **fc_startptr, bool *fc_more)
{
	char	   *fc_p;
	char	   *fc_s = *fc_startptr;
	char	   *fc_e;
	int			fc_len;

	/*
	 * 查找当前元素的结束；逗号或字符串末尾
	 * 作为终止符。
	 */
	fc_e = fc_s;
	while (*fc_e != '\0' && *fc_e != ',')
		++fc_e;
	*fc_more = (*fc_e == ',');

	fc_len = fc_e - fc_s;
	fc_p = (char *) malloc(sizeof(char) * (fc_len + 1));
	if (fc_p)
	{
		memcpy(fc_p, fc_s, fc_len);
		fc_p[fc_len] = '\0';
	}
	*fc_startptr = fc_e + 1;

	return fc_p;
}

/*
 *		connectOptions2
 *
 * 在吸收所有用户提供的信息后计算派生连接选项。
 *
 * 如果成功返回 true，遇到问题返回 false （在这种情况下会设置 errorMessage 和 conn->status）。
 */
static bool fc_connectOptions2(PGconn *fc_conn)
{
	int			fc_i;

	/*
	 * 为我们可能尝试连接的每个主机分配内存详细信息。为此，计数 hostaddr
	 * 或 host 选项中的元素数量。如果两者都没有给出，则假定为一个主机。
	 */
	fc_conn->whichhost = 0;
	if (fc_conn->pghostaddr && fc_conn->pghostaddr[0] != '\0')
		fc_conn->nconnhost = fc_count_comma_separated_elems(fc_conn->pghostaddr);
	else if (fc_conn->pghost && fc_conn->pghost[0] != '\0')
		fc_conn->nconnhost = fc_count_comma_separated_elems(fc_conn->pghost);
	else
		fc_conn->nconnhost = 1;
	fc_conn->connhost = (pg_conn_host *)
		calloc(fc_conn->nconnhost, sizeof(pg_conn_host));
	if (fc_conn->connhost == NULL)
		goto oom_error;

	/*
	 * 现在，我们为每个可能的主机有一个 pg_conn_host 结构。通过拆分参数字符串填写
	 * 每个主机的 host 和 hostaddr 字段。
	 */
	if (fc_conn->pghostaddr != NULL && fc_conn->pghostaddr[0] != '\0')
	{
		char	   *fc_s = fc_conn->pghostaddr;
		bool		fc_more = true;

		for (fc_i = 0; fc_i < fc_conn->nconnhost && fc_more; fc_i++)
		{
			fc_conn->connhost[fc_i].hostaddr = fc_parse_comma_separated_list(&fc_s, &fc_more);
			if (fc_conn->connhost[fc_i].hostaddr == NULL)
				goto oom_error;
		}

		/*
		 * 如果给出了 hostaddr，那么数组的分配是根据
		 * hostaddr 列表中的元素数量，因此它应该是
		 * 正确的大小。
		 */
		Assert(!fc_more);
		Assert(fc_i == fc_conn->nconnhost);
	}

	if (fc_conn->pghost != NULL && fc_conn->pghost[0] != '\0')
	{
		char	   *fc_s = fc_conn->pghost;
		bool		fc_more = true;

		for (fc_i = 0; fc_i < fc_conn->nconnhost && fc_more; fc_i++)
		{
			fc_conn->connhost[fc_i].host = fc_parse_comma_separated_list(&fc_s, &fc_more);
			if (fc_conn->connhost[fc_i].host == NULL)
				goto oom_error;
		}

		/* 检查主机项目数量不正确。 */
		if (fc_more || fc_i != fc_conn->nconnhost)
		{
			fc_conn->status = CONNECTION_BAD;
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not match %d host names to %d hostaddr values\n"),
							  fc_count_comma_separated_elems(fc_conn->pghost), fc_conn->nconnhost);
			return false;
		}
	}

	/*
	 * 现在，对于每个主机插槽，识别地址规范的类型，如果没有给出则填写
	 * 默认地址。
	 */
	for (fc_i = 0; fc_i < fc_conn->nconnhost; fc_i++)
	{
		pg_conn_host *fc_ch = &fc_conn->connhost[fc_i];

		if (fc_ch->hostaddr != NULL && fc_ch->hostaddr[0] != '\0')
			fc_ch->type = CHT_HOST_ADDRESS;
		else if (fc_ch->host != NULL && fc_ch->host[0] != '\0')
		{
			fc_ch->type = CHT_HOST_NAME;
#ifdef HAVE_UNIX_SOCKETS
			if (is_unixsock_path(fc_ch->host))
				fc_ch->type = CHT_UNIX_SOCKET;
#endif
		}
		else
		{
			if (fc_ch->host)
				free(fc_ch->host);

			/*
			 * 这一部分选择默认主机位置。如果你更改
			 * 这个，请参见 pg_regress。
			 */
#ifdef HAVE_UNIX_SOCKETS
			if (DEFAULT_PGSOCKET_DIR[0])
			{
				fc_ch->host = strdup(DEFAULT_PGSOCKET_DIR);
				fc_ch->type = CHT_UNIX_SOCKET;
			}
			else
#endif
			{
				fc_ch->host = strdup(DefaultHost);
				fc_ch->type = CHT_HOST_NAME;
			}
			if (fc_ch->host == NULL)
				goto oom_error;
		}
	}

	/*
	 * 接下来，计算每个主机名对应的端口号。
	 *
	 * 注意：与上述主机名不同，这可能将端口字段
	 * 留为空或空字符串。我们将在读取这样的端口字段时
	 * 替换为 DEF_PGPORT。
	 */
	if (fc_conn->pgport != NULL && fc_conn->pgport[0] != '\0')
	{
		char	   *fc_s = fc_conn->pgport;
		bool		fc_more = true;

		for (fc_i = 0; fc_i < fc_conn->nconnhost && fc_more; fc_i++)
		{
			fc_conn->connhost[fc_i].port = fc_parse_comma_separated_list(&fc_s, &fc_more);
			if (fc_conn->connhost[fc_i].port == NULL)
				goto oom_error;
		}

		/*
		 * 如果仅给出了一个端口，则为每个主机使用该端口。否则，
		 * 端口数量必须与主机数量相等。
		 */
		if (fc_i == 1 && !fc_more)
		{
			for (fc_i = 1; fc_i < fc_conn->nconnhost; fc_i++)
			{
				fc_conn->connhost[fc_i].port = strdup(fc_conn->connhost[0].port);
				if (fc_conn->connhost[fc_i].port == NULL)
					goto oom_error;
			}
		}
		else if (fc_more || fc_i != fc_conn->nconnhost)
		{
			fc_conn->status = CONNECTION_BAD;
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not match %d port numbers to %d hosts\n"),
							  fc_count_comma_separated_elems(fc_conn->pgport), fc_conn->nconnhost);
			return false;
		}
	}

	/*
	 * 如果未给出用户名，请获取它。 （最有可能的是，此获取将
	 * 失败，因为我们到达这里的唯一方式是 pg_fe_getauthname() 在
	 * conninfo_add_defaults() 期间失败。 但现在我们想要一条错误消息。）
	 */
	if (fc_conn->pguser == NULL || fc_conn->pguser[0] == '\0')
	{
		if (fc_conn->pguser)
			free(fc_conn->pguser);
		fc_conn->pguser = pg_fe_getauthname(&fc_conn->errorMessage);
		if (!fc_conn->pguser)
		{
			fc_conn->status = CONNECTION_BAD;
			return false;
		}
	}

	/*
	 * 如果未给出数据库名称，则默认将其设为与用户名相等
	 */
	if (fc_conn->dbName == NULL || fc_conn->dbName[0] == '\0')
	{
		if (fc_conn->dbName)
			free(fc_conn->dbName);
		fc_conn->dbName = strdup(fc_conn->pguser);
		if (!fc_conn->dbName)
			goto oom_error;
	}

	/*
	 * 如果没有提供密码，尝试在密码文件中查找。注意
	 * 结果可能因每个主机/端口对而异。
	 */
	if (fc_conn->pgpass == NULL || fc_conn->pgpass[0] == '\0')
	{
		/* 如果没有指定密码文件，使用 ~/PGPASSFILE */
		if (fc_conn->pgpassfile == NULL || fc_conn->pgpassfile[0] == '\0')
		{
			char		fc_homedir[MAXPGPATH];

			if (pqGetHomeDirectory(fc_homedir, sizeof(fc_homedir)))
			{
				if (fc_conn->pgpassfile)
					free(fc_conn->pgpassfile);
				fc_conn->pgpassfile = malloc(MAXPGPATH);
				if (!fc_conn->pgpassfile)
					goto oom_error;
				snprintf(fc_conn->pgpassfile, MAXPGPATH, "%s/%s",
						 fc_homedir, PGPASSFILE);
			}
		}

		if (fc_conn->pgpassfile != NULL && fc_conn->pgpassfile[0] != '\0')
		{
			for (fc_i = 0; fc_i < fc_conn->nconnhost; fc_i++)
			{
				/*
				 * 尝试从文件中为此主机获取密码。如果给定则使用主机
				 * 作为主机名搜索键，否则使用主机地址（此时至少有一个
				 * 被保证非空）。
				 */
				const char *fc_pwhost = fc_conn->connhost[fc_i].host;

				if (fc_pwhost == NULL || fc_pwhost[0] == '\0')
					fc_pwhost = fc_conn->connhost[fc_i].hostaddr;

				fc_conn->connhost[fc_i].password =
					fc_passwordFromFile(fc_pwhost,
									 fc_conn->connhost[fc_i].port,
									 fc_conn->dbName,
									 fc_conn->pguser,
									 fc_conn->pgpassfile);
			}
		}
	}

	/*
	 * 验证 channel_binding 选项
	 */
	if (fc_conn->channel_binding)
	{
		if (strcmp(fc_conn->channel_binding, "disable") != 0
			&& strcmp(fc_conn->channel_binding, "prefer") != 0
			&& strcmp(fc_conn->channel_binding, "require") != 0)
		{
			fc_conn->status = CONNECTION_BAD;
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("invalid %s value: \"%s\"\n"),
							  "channel_binding", fc_conn->channel_binding);
			return false;
		}
	}
	else
	{
		fc_conn->channel_binding = strdup(DefaultChannelBinding);
		if (!fc_conn->channel_binding)
			goto oom_error;
	}

	/*
	 * 验证 sslmode 选项
	 */
	if (fc_conn->sslmode)
	{
		if (strcmp(fc_conn->sslmode, "disable") != 0
			&& strcmp(fc_conn->sslmode, "allow") != 0
			&& strcmp(fc_conn->sslmode, "prefer") != 0
			&& strcmp(fc_conn->sslmode, "require") != 0
			&& strcmp(fc_conn->sslmode, "verify-ca") != 0
			&& strcmp(fc_conn->sslmode, "verify-full") != 0)
		{
			fc_conn->status = CONNECTION_BAD;
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("invalid %s value: \"%s\"\n"),
							  "sslmode", fc_conn->sslmode);
			return false;
		}

#ifndef USE_SSL
		switch (fc_conn->sslmode[0])
		{
			case 'a':			/* "allow" */
			case 'p':			/* "prefer" */

				/*
				 * 警告用户 SSL 连接将永远不会被协商
				 * 因为 SSL 没有被编译进？
				 */
				break;

			case 'r':			/* "require" */
			case 'v':			/* "verify-ca" 或 "verify-full" */
				fc_conn->status = CONNECTION_BAD;
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("sslmode value \"%s\" invalid when SSL support is not compiled in\n"),
								  fc_conn->sslmode);
				return false;
		}
#endif
	}
	else
	{
		fc_conn->sslmode = strdup(DefaultSSLMode);
		if (!fc_conn->sslmode)
			goto oom_error;
	}

	/*
	 * 验证 ssl_min_protocol_version 和
	 * ssl_max_protocol_version 的 TLS 协议版本。
	 */
	if (!fc_sslVerifyProtocolVersion(fc_conn->ssl_min_protocol_version))
	{
		fc_conn->status = CONNECTION_BAD;
		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("invalid %s value: \"%s\"\n"),
						  "ssl_min_protocol_version",
						  fc_conn->ssl_min_protocol_version);
		return false;
	}
	if (!fc_sslVerifyProtocolVersion(fc_conn->ssl_max_protocol_version))
	{
		fc_conn->status = CONNECTION_BAD;
		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("invalid %s value: \"%s\"\n"),
						  "ssl_max_protocol_version",
						  fc_conn->ssl_max_protocol_version);
		return false;
	}

	/*
	 * 检查定义的 SSL 协议范围是否正确。这在这个早期步骤中完成
	 * 因为这与使用的 SSL 实现无关，并且在建立连接时避免
	 * 不必要的循环，因为无论如何它都注定会失败。
	 */
	if (!fc_sslVerifyProtocolRange(fc_conn->ssl_min_protocol_version,
								fc_conn->ssl_max_protocol_version))
	{
		fc_conn->status = CONNECTION_BAD;
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("invalid SSL protocol version range\n"));
		return false;
	}

	/*
	 * 验证 gssencmode 选项
	 */
	if (fc_conn->gssencmode)
	{
		if (strcmp(fc_conn->gssencmode, "disable") != 0 &&
			strcmp(fc_conn->gssencmode, "prefer") != 0 &&
			strcmp(fc_conn->gssencmode, "require") != 0)
		{
			fc_conn->status = CONNECTION_BAD;
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("invalid %s value: \"%s\"\n"),
							  "gssencmode",
							  fc_conn->gssencmode);
			return false;
		}
#ifndef ENABLE_GSS
		if (strcmp(fc_conn->gssencmode, "require") == 0)
		{
			fc_conn->status = CONNECTION_BAD;
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("gssencmode value \"%s\" invalid when GSSAPI support is not compiled in\n"),
							  fc_conn->gssencmode);
			return false;
		}
#endif
	}
	else
	{
		fc_conn->gssencmode = strdup(DefaultGSSMode);
		if (!fc_conn->gssencmode)
			goto oom_error;
	}

	/*
	 * 验证 target_session_attrs 选项，并设置 target_server_type
	 */
	if (fc_conn->target_session_attrs)
	{
		if (strcmp(fc_conn->target_session_attrs, "any") == 0)
			fc_conn->target_server_type = SERVER_TYPE_ANY;
		else if (strcmp(fc_conn->target_session_attrs, "read-write") == 0)
			fc_conn->target_server_type = SERVER_TYPE_READ_WRITE;
		else if (strcmp(fc_conn->target_session_attrs, "read-only") == 0)
			fc_conn->target_server_type = SERVER_TYPE_READ_ONLY;
		else if (strcmp(fc_conn->target_session_attrs, "primary") == 0)
			fc_conn->target_server_type = SERVER_TYPE_PRIMARY;
		else if (strcmp(fc_conn->target_session_attrs, "standby") == 0)
			fc_conn->target_server_type = SERVER_TYPE_STANDBY;
		else if (strcmp(fc_conn->target_session_attrs, "prefer-standby") == 0)
			fc_conn->target_server_type = SERVER_TYPE_PREFER_STANDBY;
		else
		{
			fc_conn->status = CONNECTION_BAD;
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("invalid %s value: \"%s\"\n"),
							  "target_session_attrs",
							  fc_conn->target_session_attrs);
			return false;
		}
	}
	else
		fc_conn->target_server_type = SERVER_TYPE_ANY;

	/*
	 * 从区域设置解析特殊的 "auto" client_encoding
	 */
	if (fc_conn->client_encoding_initial &&
		strcmp(fc_conn->client_encoding_initial, "auto") == 0)
	{
		free(fc_conn->client_encoding_initial);
		fc_conn->client_encoding_initial = strdup(pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true)));
		if (!fc_conn->client_encoding_initial)
			goto oom_error;
	}

	/*
	 * 只有当我们走到这一步时，才适合尝试连接。（我们需要一个
	 * 状态标志，而不仅仅是此函数的布尔结果，以防有人尝试
	 * PQreset() PGconn。）
	 */
	fc_conn->options_valid = true;

	return true;

oom_error:
	fc_conn->status = CONNECTION_BAD;
	appendPQExpBufferStr(&fc_conn->errorMessage,
						 libpq_gettext("out of memory\n"));
	return false;
}

/*
 *		PQconndefaults
 *
 * 构造一个默认连接选项数组，识别所有
 * 可用选项并显示从环境等获得的任何默认值。
 * 在出错时（例如内存不足），返回 NULL。
 *
 * 使用此函数，应用程序可以确定所有可能的选项
 * 及其当前默认值。
 *
 * 注意：自 PostgreSQL 7.0 以来，返回的数组是动态分配的
 * 应在不再需要时通过 PQconninfoFree() 释放。 （在之前
 * 的版本中，返回的数组是静态的，但这不是线程安全的。）
 * 使用此函数的 7.0 之前的应用程序将在更新为调用
 * PQconninfoFree 之前看到小的内存泄漏。
 */
PQconninfoOption * PQconndefaults(void)
{
	PQExpBufferData fc_errorBuf;
	PQconninfoOption *fc_connOptions;

	/* 我们实际上在这里不报告任何错误，但被调用者需要一个缓冲区 */
	initPQExpBuffer(&fc_errorBuf);
	if (PQExpBufferDataBroken(fc_errorBuf))
		return NULL;			/* 内存不足 :-( */

	fc_connOptions = fc_conninfo_init(&fc_errorBuf);
	if (fc_connOptions != NULL)
	{
		/* 传递 NULL errorBuf 来忽略错误 */
		if (!fc_conninfo_add_defaults(fc_connOptions, NULL))
		{
			PQconninfoFree(fc_connOptions);
			fc_connOptions = NULL;
		}
	}

	termPQExpBuffer(&fc_errorBuf);
	return fc_connOptions;
}

/* ----------------
 *		PQsetdbLogin
 *
 * 通过指定的主机和端口在 postmaster 上建立与 postgres 后端的连接。
 *
 * 返回一个 PGconn* ，该连接在后续的 libpq 调用中是必需的
 *
 * 如果返回的连接的状态字段为 CONNECTION_BAD，
 * 则只有 errorMessage 可能有用。
 * ----------------
 */
PGconn * PQsetdbLogin(const char *fc_pghost, const char *fc_pgport, const char *fc_pgoptions,
			 const char *fc_pgtty, const char *fc_dbName, const char *fc_login,
			 const char *fc_pwd)
{
	PGconn	   *fc_conn;

	/*
	 * 为conn结构分配内存。请注意，我们还希望将conn->errorMessage初始化为空。所有 subsequent steps during
	 * 连接初始化将仅附加到该缓冲区。
	 */
	fc_conn = fc_makeEmptyPGconn();
	if (fc_conn == NULL)
		return NULL;

	/*
	 * 如果 dbName 参数包含看似连接字符串的内容，使用 connectOptions1 解析它为 conn 结构。
	 */
	if (fc_dbName && fc_recognized_connection_string(fc_dbName))
	{
		if (!fc_connectOptions1(fc_conn, fc_dbName))
			return fc_conn;
	}
	else
	{
		/*
		 * 旧式路径：首先，解析一个空的 conninfo 字符串，以便设置 PQconnectdb() 将使用的相同默认值。
		 */
		if (!fc_connectOptions1(fc_conn, ""))
			return fc_conn;

		/* 将 dbName 参数值插入结构 */
		if (fc_dbName && fc_dbName[0] != '\0')
		{
			if (fc_conn->dbName)
				free(fc_conn->dbName);
			fc_conn->dbName = strdup(fc_dbName);
			if (!fc_conn->dbName)
				goto oom_error;
		}
	}

	/*
	 * 将剩余参数插入结构，覆盖默认值（以及从 dbName 作为 conninfo 获取的任何冲突数据）。
	 */
	if (fc_pghost && fc_pghost[0] != '\0')
	{
		if (fc_conn->pghost)
			free(fc_conn->pghost);
		fc_conn->pghost = strdup(fc_pghost);
		if (!fc_conn->pghost)
			goto oom_error;
	}

	if (fc_pgport && fc_pgport[0] != '\0')
	{
		if (fc_conn->pgport)
			free(fc_conn->pgport);
		fc_conn->pgport = strdup(fc_pgport);
		if (!fc_conn->pgport)
			goto oom_error;
	}

	if (fc_pgoptions && fc_pgoptions[0] != '\0')
	{
		if (fc_conn->pgoptions)
			free(fc_conn->pgoptions);
		fc_conn->pgoptions = strdup(fc_pgoptions);
		if (!fc_conn->pgoptions)
			goto oom_error;
	}

	if (fc_login && fc_login[0] != '\0')
	{
		if (fc_conn->pguser)
			free(fc_conn->pguser);
		fc_conn->pguser = strdup(fc_login);
		if (!fc_conn->pguser)
			goto oom_error;
	}

	if (fc_pwd && fc_pwd[0] != '\0')
	{
		if (fc_conn->pgpass)
			free(fc_conn->pgpass);
		fc_conn->pgpass = strdup(fc_pwd);
		if (!fc_conn->pgpass)
			goto oom_error;
	}

	/*
	 * 计算派生选项
	 */
	if (!fc_connectOptions2(fc_conn))
		return fc_conn;

	/*
	 * 连接到数据库
	 */
	if (fc_connectDBStart(fc_conn))
		(void) fc_connectDBComplete(fc_conn);

	return fc_conn;

oom_error:
	fc_conn->status = CONNECTION_BAD;
	appendPQExpBufferStr(&fc_conn->errorMessage,
						 libpq_gettext("out of memory\n"));
	return fc_conn;
}


/* ----------
 * connectNoDelay -
 * 设置 TCP_NODELAY 套接字选项。
 * 如果成功返回 1, 否则返回 0。
 * ----------
 */
static int fc_connectNoDelay(PGconn *fc_conn)
{
#ifdef	TCP_NODELAY
	int			fc_on = 1;

	if (setsockopt(fc_conn->sock, IPPROTO_TCP, TCP_NODELAY,
				   (char *) &fc_on,
				   sizeof(fc_on)) < 0)
	{
		char		fc_sebuf[PG_STRERROR_R_BUFLEN];

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("could not set socket to TCP no delay mode: %s\n"),
						  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
		return 0;
	}
#endif

	return 1;
}

/* ----------
 * 将当前连接的 IP 地址写入 host_addr（长度为 host_addr_len）。
 * 如果无法，将其设置为空字符串。
 * ----------
 */
static void fc_getHostaddr(PGconn *fc_conn, char *fc_host_addr, int fc_host_addr_len)
{
	struct sockaddr_storage *fc_addr = &fc_conn->raddr.addr;

	if (fc_addr->ss_family == AF_INET)
	{
		if (pg_inet_net_ntop(AF_INET,
							 &((struct sockaddr_in *) fc_addr)->sin_addr.s_addr,
							 32,
							 fc_host_addr, fc_host_addr_len) == NULL)
			fc_host_addr[0] = '\0';
	}
#ifdef HAVE_IPV6
	else if (fc_addr->ss_family == AF_INET6)
	{
		if (pg_inet_net_ntop(AF_INET6,
							 &((struct sockaddr_in6 *) fc_addr)->sin6_addr.s6_addr,
							 128,
							 fc_host_addr, fc_host_addr_len) == NULL)
			fc_host_addr[0] = '\0';
	}
#endif
	else
		fc_host_addr[0] = '\0';
}

/*
 * emitHostIdentityInfo -
 * 一旦我们识别出当前连接目标地址，投机性地将“连接到服务器某某失败：“附加到 conn->errorMessage。这确保任何后续错误消息将正确归因于我们无法连接的服务器。 conn->raddr 必须有效，并且必须提供 getHostaddr() 的结果。
 */
static void fc_emitHostIdentityInfo(PGconn *fc_conn, const char *fc_host_addr)
{
#ifdef HAVE_UNIX_SOCKETS
	if (fc_conn->raddr.addr.ss_family == AF_UNIX)
	{
		char		fc_service[NI_MAXHOST];

		pg_getnameinfo_all(&fc_conn->raddr.addr, fc_conn->raddr.salen,
						   NULL, 0,
						   fc_service, sizeof(fc_service),
						   NI_NUMERICSERV);
		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("connection to server on socket \"%s\" failed: "),
						  fc_service);
	}
	else
#endif							/* HAVE_UNIX_SOCKETS */
	{
		const char *fc_displayed_host;
		const char *fc_displayed_port;

		/* 我们实际连接的是哪个主机和端口？ */
		if (fc_conn->connhost[fc_conn->whichhost].type == CHT_HOST_ADDRESS)
			fc_displayed_host = fc_conn->connhost[fc_conn->whichhost].hostaddr;
		else
			fc_displayed_host = fc_conn->connhost[fc_conn->whichhost].host;
		fc_displayed_port = fc_conn->connhost[fc_conn->whichhost].port;
		if (fc_displayed_port == NULL || fc_displayed_port[0] == '\0')
			fc_displayed_port = DEF_PGPORT_STR;

		/*
		 * 如果用户没有使用 'hostaddr' 提供 IP 地址，并且 'host' 缺失或与我们的查找不匹配，显示查找到的 IP 地址。
		 */
		if (fc_conn->connhost[fc_conn->whichhost].type != CHT_HOST_ADDRESS &&
			fc_host_addr[0] &&
			strcmp(fc_displayed_host, fc_host_addr) != 0)
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("connection to server at \"%s\" (%s), port %s failed: "),
							  fc_displayed_host, fc_host_addr,
							  fc_displayed_port);
		else
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("connection to server at \"%s\", port %s failed: "),
							  fc_displayed_host,
							  fc_displayed_port);
	}
}

/* ----------
 * connectFailureMessage -
 * 在连接失败时创建友好的错误消息，
 * 使用给定的 errno 值。对于隐含没有服务器的错误情况使用此消息。
 * ----------
 */
static void fc_connectFailureMessage(PGconn *fc_conn, int fc_errorno)
{
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];

	appendPQExpBuffer(&fc_conn->errorMessage,
					  "%s\n",
					  SOCK_STRERROR(fc_errorno, fc_sebuf, sizeof(fc_sebuf)));

#ifdef HAVE_UNIX_SOCKETS
	if (fc_conn->raddr.addr.ss_family == AF_UNIX)
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("\tIs the server running locally and accepting connections on that socket?\n"));
	else
#endif
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("\tIs the server running on that host and accepting TCP/IP connections?\n"));
}

/*
 * 我们是否应该使用保活？如果是返回 1，否返回 0，且如果 conn->keepalives 设置为不可解析为整数的值则返回 -1。
 */
static int fc_useKeepalives(PGconn *fc_conn)
{
	int			fc_val;

	if (fc_conn->keepalives == NULL)
		return 1;

	if (!fc_parse_int_param(fc_conn->keepalives, &fc_val, fc_conn, "keepalives"))
		return -1;

	return fc_val != 0 ? 1 : 0;
}

/*
 * 解析并尝试将“值”解释为整数值，如果成功，将其存储在 *result 中，如果有任何尾随垃圾或溢出则抱怨。这允许任何数量的前后空格。
 */
static bool fc_parse_int_param(const char *fc_value, int *fc_result, PGconn *fc_conn,
				const char *fc_context)
{
	char	   *fc_end;
	long		fc_numval;

	Assert(fc_value != NULL);

	*fc_result = 0;

	/* strtol(3) 跳过前导空格 */
	errno = 0;
	fc_numval = strtol(fc_value, &fc_end, 10);

	/*
	 * 如果在解析期间未进行任何进展或发生错误，则失败。
	 * 这会正确测试结果的溢出情况。
	 */
	if (fc_value == fc_end || errno != 0 || fc_numval != (int) fc_numval)
		goto error;

	/*
	 * 跳过任何尾随空格；如果在终止字符之前仍然有任何非空白字符，则失败。
	 */
	while (*fc_end != '\0' && isspace((unsigned char) *fc_end))
		fc_end++;

	if (*fc_end != '\0')
		goto error;

	*fc_result = fc_numval;
	return true;

error:
	appendPQExpBuffer(&fc_conn->errorMessage,
					  libpq_gettext("invalid integer value \"%s\" for connection option \"%s\"\n"),
					  fc_value, fc_context);
	return false;
}

#ifndef WIN32
/*
 * 设置保活空闲定时器。
 */
static int fc_setKeepalivesIdle(PGconn *fc_conn)
{
	int			fc_idle;

	if (fc_conn->keepalives_idle == NULL)
		return 1;

	if (!fc_parse_int_param(fc_conn->keepalives_idle, &fc_idle, fc_conn,
						 "keepalives_idle"))
		return 0;
	if (fc_idle < 0)
		fc_idle = 0;

#ifdef PG_TCP_KEEPALIVE_IDLE
	if (setsockopt(fc_conn->sock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
				   (char *) &fc_idle, sizeof(fc_idle)) < 0)
	{
		char		fc_sebuf[PG_STRERROR_R_BUFLEN];

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("%s(%s) failed: %s\n"),
						  "setsockopt",
						  PG_TCP_KEEPALIVE_IDLE_STR,
						  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
		return 0;
	}
#endif

	return 1;
}

/*
 * 设置保活间隔。
 */
static int fc_setKeepalivesInterval(PGconn *fc_conn)
{
	int			fc_interval;

	if (fc_conn->keepalives_interval == NULL)
		return 1;

	if (!fc_parse_int_param(fc_conn->keepalives_interval, &fc_interval, fc_conn,
						 "keepalives_interval"))
		return 0;
	if (fc_interval < 0)
		fc_interval = 0;

#ifdef TCP_KEEPINTVL
	if (setsockopt(fc_conn->sock, IPPROTO_TCP, TCP_KEEPINTVL,
				   (char *) &fc_interval, sizeof(fc_interval)) < 0)
	{
		char		fc_sebuf[PG_STRERROR_R_BUFLEN];

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("%s(%s) failed: %s\n"),
						  "setsockopt",
						  "TCP_KEEPINTVL",
						  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
		return 0;
	}
#endif

	return 1;
}

/*
 * 设置丢失的保活数据包计数，这将触发连接中断。
 */
static int fc_setKeepalivesCount(PGconn *fc_conn)
{
	int			fc_count;

	if (fc_conn->keepalives_count == NULL)
		return 1;

	if (!fc_parse_int_param(fc_conn->keepalives_count, &fc_count, fc_conn,
						 "keepalives_count"))
		return 0;
	if (fc_count < 0)
		fc_count = 0;

#ifdef TCP_KEEPCNT
	if (setsockopt(fc_conn->sock, IPPROTO_TCP, TCP_KEEPCNT,
				   (char *) &fc_count, sizeof(fc_count)) < 0)
	{
		char		fc_sebuf[PG_STRERROR_R_BUFLEN];

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("%s(%s) failed: %s\n"),
						  "setsockopt",
						  "TCP_KEEPCNT",
						  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
		return 0;
	}
#endif

	return 1;
}
#else							/* WIN32 */
#ifdef SIO_KEEPALIVE_VALS
/*
 * 在Win32上启用保活并设置保活值，
 * 因为它们总是批量设置的。
 *
 * 警告：这需要是信号安全的，因为它被PQcancel使用。
 */
static int setKeepalivesWin32(pgsocket sock, int idle, int interval)
{
	struct tcp_keepalive ka;
	DWORD		retsize;

	if (idle <= 0)
		idle = 2 * 60 * 60;		/* 2小时 = 默认 */
	if (interval <= 0)
		interval = 1;			/* 1秒 = 默认 */

	ka.onoff = 1;
	ka.keepalivetime = idle * 1000;
	ka.keepaliveinterval = interval * 1000;

	if (WSAIoctl(sock,
				 SIO_KEEPALIVE_VALS,
				 (LPVOID) &ka,
				 sizeof(ka),
				 NULL,
				 0,
				 &retsize,
				 NULL,
				 NULL)
		!= 0)
		return 0;
	return 1;
}

static int prepKeepalivesWin32(PGconn *conn)
{
	int			idle = -1;
	int			interval = -1;

	if (conn->keepalives_idle &&
		!parse_int_param(conn->keepalives_idle, &idle, conn,
						 "keepalives_idle"))
		return 0;
	if (conn->keepalives_interval &&
		!parse_int_param(conn->keepalives_interval, &interval, conn,
						 "keepalives_interval"))
		return 0;

	if (!setKeepalivesWin32(conn->sock, idle, interval))
	{
		appendPQExpBuffer(&conn->errorMessage,
						  libpq_gettext("%s(%s) failed: error code %d\n"),
						  "WSAIoctl", "SIO_KEEPALIVE_VALS",
						  WSAGetLastError());
		return 0;
	}
	return 1;
}
#endif							/* SIO_KEEPALIVE_VALS */
#endif							/* WIN32 */

/*
 * 设置TCP用户超时。
 */
static int fc_setTCPUserTimeout(PGconn *fc_conn)
{
	int			fc_timeout;

	if (fc_conn->pgtcp_user_timeout == NULL)
		return 1;

	if (!fc_parse_int_param(fc_conn->pgtcp_user_timeout, &fc_timeout, fc_conn,
						 "tcp_user_timeout"))
		return 0;

	if (fc_timeout < 0)
		fc_timeout = 0;

#ifdef TCP_USER_TIMEOUT
	if (setsockopt(fc_conn->sock, IPPROTO_TCP, TCP_USER_TIMEOUT,
				   (char *) &fc_timeout, sizeof(fc_timeout)) < 0)
	{
		char		fc_sebuf[256];

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("%s(%s) failed: %s\n"),
						  "setsockopt",
						  "TCP_USER_TIMEOUT",
						  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
		return 0;
	}
#endif

	return 1;
}

/* ----------
 * connectDBStart -
 *		开始建立与后端的连接。
 *
 * 成功时返回1，失败时返回0。
 * ----------
 */
static int fc_connectDBStart(PGconn *fc_conn)
{
	if (!fc_conn)
		return 0;

	if (!fc_conn->options_valid)
		goto connect_errReturn;

	/*
	 * 检查是否错误链接到后端内部版本的src/common
	 * 函数（请参阅link-canary.c中的注释，了解我们需要这个的原因）。
	 * 除开发人员外，其他人都不应该看到此消息，因此我们不再翻译它。
	 */
	if (!pg_link_canary_is_frontend())
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 "libpq is incorrectly linked to backend functions\n");
		goto connect_errReturn;
	}

	/* 确保我们的缓冲区是空的 */
	fc_conn->inStart = fc_conn->inCursor = fc_conn->inEnd = 0;
	fc_conn->outCount = 0;

	/*
	 * 设置以尝试连接到第一个主机。(将whichhost设置为-1有点不正当,
	 * 但PQconnectPoll会在其他任何东西查看之前将其推进到0。)
	 */
	fc_conn->whichhost = -1;
	fc_conn->try_next_addr = false;
	fc_conn->try_next_host = true;
	fc_conn->status = CONNECTION_NEEDED;

	/* 如果需要，则重置target_server_type状态 */
	if (fc_conn->target_server_type == SERVER_TYPE_PREFER_STANDBY_PASS2)
		fc_conn->target_server_type = SERVER_TYPE_PREFER_STANDBY;

	/*
	 * 处理CONNECTION_NEEDED状态的代码在PQconnectPoll()中，
	 * 以便在异步启动过程中需要时可以轻松重新执行。
	 * 不过，我们必须在这里先运行一次，因为调用者期望
	 * 该例程的成功返回意味着我们处于PGRES_POLLING_WRITING连接状态。
	 */
	if (PQconnectPoll(fc_conn) == PGRES_POLLING_WRITING)
		return 1;

connect_errReturn:

	/*
	 * 如果我们成功打开了一个套接字，请立即关闭它，
	 * 而不是等到PQfinish。 （应用程序尚未从PQsocket获得套接字，
	 * 因此，这不会冒任何风险。）
	 */
	pqDropConnection(fc_conn, true);
	fc_conn->status = CONNECTION_BAD;
	return 0;
}


/*
 *		connectDBComplete
 *
 * 阻塞并完成连接。
 *
 * 成功时返回1，失败时返回0。
 */
static int fc_connectDBComplete(PGconn *fc_conn)
{
	PostgresPollingStatusType fc_flag = PGRES_POLLING_WRITING;
	time_t		fc_finish_time = ((time_t) -1);
	int			fc_timeout = 0;
	int			fc_last_whichhost = -2;	/* 无疑与whichhost不同 */
	struct addrinfo *fc_last_addr_cur = NULL;

	if (fc_conn == NULL || fc_conn->status == CONNECTION_BAD)
		return 0;

	/*
	 * 如果connect_timeout不为零，则设置时间限制。
	 */
	if (fc_conn->connect_timeout != NULL)
	{
		if (!fc_parse_int_param(fc_conn->connect_timeout, &fc_timeout, fc_conn,
							 "connect_timeout"))
		{
			/* 标记连接为坏的以报告解析失败 */
			fc_conn->status = CONNECTION_BAD;
			return 0;
		}

		if (fc_timeout > 0)
		{
			/*
			 * 舍入可能导致连接快速意外失败；
			 * 为了防止几乎没有等待，要求至少等待
			 * 两秒钟。
			 */
			if (fc_timeout < 2)
				fc_timeout = 2;
		}
		else					/* 负值意味着0 */
			fc_timeout = 0;
	}

	for (;;)
	{
		int			fc_ret = 0;

		/*
		 * 如果connect_timeout处于活动状态并且我们正在考虑不同于上一次
		 * 的主机，则（重新）启动connect_timeout计时器。
		 * 不过，如果我们已经成功，则无需重新计算。
		 */
		if (fc_flag != PGRES_POLLING_OK &&
			fc_timeout > 0 &&
			(fc_conn->whichhost != fc_last_whichhost ||
			 fc_conn->addr_cur != fc_last_addr_cur))
		{
			fc_finish_time = time(NULL) + fc_timeout;
			fc_last_whichhost = fc_conn->whichhost;
			fc_last_addr_cur = fc_conn->addr_cur;
		}

		/*
		 * 如果需要，则等待。请注意，初始状态（就在
		 * PQconnectStart之后）是等待套接字选择用于写入。
		 */
		switch (fc_flag)
		{
			case PGRES_POLLING_OK:
				return 1;		/* 成功！ */

			case PGRES_POLLING_READING:
				fc_ret = pqWaitTimed(1, 0, fc_conn, fc_finish_time);
				if (fc_ret == -1)
				{
					/* 硬失败，例如select()问题，会中止所有操作 */
					fc_conn->status = CONNECTION_BAD;
					return 0;
				}
				break;

			case PGRES_POLLING_WRITING:
				fc_ret = pqWaitTimed(0, 1, fc_conn, fc_finish_time);
				if (fc_ret == -1)
				{
					/* 硬失败，例如select()问题，会中止所有操作 */
					fc_conn->status = CONNECTION_BAD;
					return 0;
				}
				break;

			default:
				/* 以防我们未能在PQconnectPoll中设置它 */
				fc_conn->status = CONNECTION_BAD;
				return 0;
		}

		if (fc_ret == 1)			/* connect_timeout已到期 */
		{
			/*
			 * 放弃当前服务器/地址，尝试下一个。
			 */
			fc_conn->try_next_addr = true;
			fc_conn->status = CONNECTION_NEEDED;
		}

		/*
		 * 现在尝试推进状态机。
		 */
		fc_flag = PQconnectPoll(fc_conn);
	}
}

/* ----------------
 *		PQconnectPoll
 *
 * 轮询异步连接。
 *
 * 返回一个 PostgresPollingStatusType。
 * 在调用此函数之前，使用 select(2) 确定数据何时到达..
 *
 * 无论此次调用是否失败，你都必须调用 PQfinish。
 *
 * 此函数和 PQconnectStart 的目的是允许连接在不阻塞远程 I/O 执行的情况下进行。然而，有一些注意事项：
 *
 *	 o	如果你调用 PQtrace，确保你追踪的流对象不会阻塞。
 *	 o	如果你没有为远程主机提供 IP 地址（即你提供了主机名），则 PQconnectStart 将在 gethostbyname 上阻塞。如果你使用 Unix 套接字（即既不提供主机名也不提供主机地址），你将没问题。
 *	 o	如果你的后端需要使用 Kerberos 身份验证，则必须同时提供主机名和主机地址，否则此函数可能会在 gethostname 上阻塞。
 *
 * ----------------
 */
PostgresPollingStatusType PQconnectPoll(PGconn *fc_conn)
{
	bool		fc_reset_connection_state_machine = false;
	bool		fc_need_new_connection = false;
	PGresult   *fc_res;
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];
	int			fc_optval;

	if (fc_conn == NULL)
		return PGRES_POLLING_FAILED;

	/* 获取新数据 */
	switch (fc_conn->status)
	{
			/*
			 * 在这两种情况下我们真的不应该被轮询，但我们
			 * 可以处理它。
			 */
		case CONNECTION_BAD:
			return PGRES_POLLING_FAILED;
		case CONNECTION_OK:
			return PGRES_POLLING_OK;

			/* 这些是读取状态 */
		case CONNECTION_AWAITING_RESPONSE:
		case CONNECTION_AUTH_OK:
		case CONNECTION_CHECK_WRITABLE:
		case CONNECTION_CONSUME:
		case CONNECTION_CHECK_STANDBY:
			{
				/* 加载等待数据 */
				int			fc_n = pqReadData(fc_conn);

				if (fc_n < 0)
					goto error_return;
				if (fc_n == 0)
					return PGRES_POLLING_READING;

				break;
			}

			/* 这些是写入状态，因此我们只需继续。 */
		case CONNECTION_STARTED:
		case CONNECTION_MADE:
			break;

			/* 特殊情况：在不等待的情况下继续。 */
		case CONNECTION_SSL_STARTUP:
		case CONNECTION_NEEDED:
		case CONNECTION_GSS_STARTUP:
		case CONNECTION_CHECK_TARGET:
			break;

		default:
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("invalid connection state, probably indicative of memory corruption\n"));
			goto error_return;
	}


keep_going:						/* 我们将回到这里，直到没有
								 * 剩下的工作。 */

	/* 是时候推进到下一个地址，还是如果没有更多地址则推进到下一个主机？ */
	if (fc_conn->try_next_addr)
	{
		if (fc_conn->addr_cur && fc_conn->addr_cur->ai_next)
		{
			fc_conn->addr_cur = fc_conn->addr_cur->ai_next;
			fc_reset_connection_state_machine = true;
		}
		else
			fc_conn->try_next_host = true;
		fc_conn->try_next_addr = false;
	}

	/* 是时候推进到下一个 connhost[] 条目？ */
	if (fc_conn->try_next_host)
	{
		pg_conn_host *fc_ch;
		struct addrinfo fc_hint;
		int			fc_thisport;
		int			fc_ret;
		char		fc_portstr[MAXPGPATH];

		if (fc_conn->whichhost + 1 < fc_conn->nconnhost)
			fc_conn->whichhost++;
		else
		{
			/*
			 * 哎呀，没有更多主机了。
			 *
			 * 如果我们尝试以“优先备用”模式连接，则放弃
			 * 备用要求并重新开始。
			 *
			 * 否则，适当的错误信息已经设置好，因此
			 * 我们只需要设置正确的状态。
			 */
			if (fc_conn->target_server_type == SERVER_TYPE_PREFER_STANDBY &&
				fc_conn->nconnhost > 0)
			{
				fc_conn->target_server_type = SERVER_TYPE_PREFER_STANDBY_PASS2;
				fc_conn->whichhost = 0;
			}
			else
				goto error_return;
		}

		/* 放弃之前主机的任何地址信息 */
		fc_release_conn_addrinfo(fc_conn);

		/*
		 * 查找新主机的信息。失败时，将问题记录到
		 * conn->errorMessage，然后循环尝试下一个主机。 （注意
		 * 我们直到成功才不会清除 try_next_host。）
		 */
		fc_ch = &fc_conn->connhost[fc_conn->whichhost];

		/* 初始化提示结构 */
		MemSet(&fc_hint, 0, sizeof(fc_hint));
		fc_hint.ai_socktype = SOCK_STREAM;
		fc_conn->addrlist_family = fc_hint.ai_family = AF_UNSPEC;

		/* 找出我们将要使用的端口号。 */
		if (fc_ch->port == NULL || fc_ch->port[0] == '\0')
			fc_thisport = DEF_PGPORT;
		else
		{
			if (!fc_parse_int_param(fc_ch->port, &fc_thisport, fc_conn, "port"))
				goto error_return;

			if (fc_thisport < 1 || fc_thisport > 65535)
			{
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("invalid port number: \"%s\"\n"),
								  fc_ch->port);
				goto keep_going;
			}
		}
		snprintf(fc_portstr, sizeof(fc_portstr), "%d", fc_thisport);

		/* 使用 pg_getaddrinfo_all() 解析地址 */
		switch (fc_ch->type)
		{
			case CHT_HOST_NAME:
				fc_ret = pg_getaddrinfo_all(fc_ch->host, fc_portstr, &fc_hint,
										 &fc_conn->addrlist);
				if (fc_ret || !fc_conn->addrlist)
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not translate host name \"%s\" to address: %s\n"),
									  fc_ch->host, gai_strerror(fc_ret));
					goto keep_going;
				}
				break;

			case CHT_HOST_ADDRESS:
				fc_hint.ai_flags = AI_NUMERICHOST;
				fc_ret = pg_getaddrinfo_all(fc_ch->hostaddr, fc_portstr, &fc_hint,
										 &fc_conn->addrlist);
				if (fc_ret || !fc_conn->addrlist)
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not parse network address \"%s\": %s\n"),
									  fc_ch->hostaddr, gai_strerror(fc_ret));
					goto keep_going;
				}
				break;

			case CHT_UNIX_SOCKET:
#ifdef HAVE_UNIX_SOCKETS
				fc_conn->addrlist_family = fc_hint.ai_family = AF_UNIX;
				UNIXSOCK_PATH(fc_portstr, fc_thisport, fc_ch->host);
				if (strlen(fc_portstr) >= UNIXSOCK_PATH_BUFLEN)
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("Unix-domain socket path \"%s\" is too long (maximum %d bytes)\n"),
									  fc_portstr,
									  (int) (UNIXSOCK_PATH_BUFLEN - 1));
					goto keep_going;
				}

				/*
				 * NULL 主机名告诉 pg_getaddrinfo_all 将服务
				 * 名称解析为 Unix 域套接字路径。
				 */
				fc_ret = pg_getaddrinfo_all(NULL, fc_portstr, &fc_hint,
										 &fc_conn->addrlist);
				if (fc_ret || !fc_conn->addrlist)
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not translate Unix-domain socket path \"%s\" to address: %s\n"),
									  fc_portstr, gai_strerror(fc_ret));
					goto keep_going;
				}
#else
				Assert(false);
#endif
				break;
		}

		/* 好的，扫描这个 addrlist 寻找可用的服务器地址 */
		fc_conn->addr_cur = fc_conn->addrlist;
		fc_reset_connection_state_machine = true;
		fc_conn->try_next_host = false;
	}

	/* 重置连接状态机？ */
	if (fc_reset_connection_state_machine)
	{
		/*
		 * （重新）初始化我们的连接控制变量以进行
		 * 到单个服务器地址的一组连接尝试。这些变量
		 * 必须在单个连接尝试之间持续存在，但我们必须
		 * 在开始考虑新服务器时重置它们。
		 */
		fc_conn->pversion = PG_PROTOCOL(3, 0);
		fc_conn->send_appname = true;
#ifdef USE_SSL
		/* 根据 SSL 模式初始化这些值 */
		fc_conn->allow_ssl_try = (fc_conn->sslmode[0] != 'd');	/* "禁用" */
		fc_conn->wait_ssl_try = (fc_conn->sslmode[0] == 'a'); /* "allow" */
#endif
#ifdef ENABLE_GSS
		fc_conn->try_gss = (fc_conn->gssencmode[0] != 'd');	/* "禁用" */
#endif

		fc_reset_connection_state_machine = false;
		fc_need_new_connection = true;
	}

	/* 强制建立新连接（也许是之前同一服务器）？ */
	if (fc_need_new_connection)
	{
		/* 放弃任何现有连接 */
		pqDropConnection(fc_conn, true);

		/* 重置从旧服务器获得的所有状态 */
		fc_pqDropServerData(fc_conn);

		/* 也丢弃我们可能拥有的任何 PGresult */
		fc_conn->asyncStatus = PGASYNC_IDLE;
		fc_conn->xactStatus = PQTRANS_IDLE;
		fc_conn->pipelineStatus = PQ_PIPELINE_OFF;
		pqClearAsyncResult(fc_conn);

		/* 重置 conn->status 以将状态机置于正确状态 */
		fc_conn->status = CONNECTION_NEEDED;

		fc_need_new_connection = false;
	}

	/* 现在尝试推动此连接的状态机 */
	switch (fc_conn->status)
	{
		case CONNECTION_NEEDED:
			{
				/*
				 * 尝试与 pg_getaddrinfo_all() 返回的地址之一建立连接。
				 * conn->addr_cur 是下一个尝试的地址。
				 *
				 * 这里的额外括号是历史遗留的。重新缩排整个 switch case 以去除它们并不值得。
				 */
				{
					struct addrinfo *fc_addr_cur = fc_conn->addr_cur;
					char		fc_host_addr[NI_MAXHOST];

					/*
					 * 如果我们已尝试当前主机的所有地址，则转到下一个可能的主机。
					 */
					if (fc_addr_cur == NULL)
					{
						fc_conn->try_next_host = true;
						goto keep_going;
					}

					/* 记住当前地址以便可能稍后使用 */
					memcpy(&fc_conn->raddr.addr, fc_addr_cur->ai_addr,
						   fc_addr_cur->ai_addrlen);
					fc_conn->raddr.salen = fc_addr_cur->ai_addrlen;

					/*
					 * 也设置 connip。请注意，我们故意忽略 strdup 失败；
					 * 如果失败也不是大问题。
					 */
					if (fc_conn->connip != NULL)
					{
						free(fc_conn->connip);
						fc_conn->connip = NULL;
					}
					fc_getHostaddr(fc_conn, fc_host_addr, NI_MAXHOST);
					if (fc_host_addr[0])
						fc_conn->connip = strdup(fc_host_addr);

					/* 尝试创建套接字 */
					fc_conn->sock = socket(fc_addr_cur->ai_family, SOCK_STREAM, 0);
					if (fc_conn->sock == PGINVALID_SOCKET)
					{
						int			fc_errorno = SOCK_ERRNO;

						/*
						 * 如果我们还有更多地址可以尝试，则静默忽略 socket() 失败；
						 * 这减少了在地址列表包含 IPv4 和 IPv6 但内核只接受一个族的情况下无谓的输出。
						 */
						if (fc_addr_cur->ai_next != NULL ||
							fc_conn->whichhost + 1 < fc_conn->nconnhost)
						{
							fc_conn->try_next_addr = true;
							goto keep_going;
						}
						fc_emitHostIdentityInfo(fc_conn, fc_host_addr);
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("could not create socket: %s\n"),
										  SOCK_STRERROR(fc_errorno, fc_sebuf, sizeof(fc_sebuf)));
						goto error_return;
					}

					/*
					 * 一旦我们确定了目标地址，除了前面的 socket() 失败情况外，
					 * 所有错误都应该以主机身份信息为前缀。 （如果连接成功，
					 * conn->errorMessage 的内容就无关紧要，因此这是无害的。）
					 */
					fc_emitHostIdentityInfo(fc_conn, fc_host_addr);

					/*
					 * 选择套接字选项：对于 TCP 套接字不延迟发送数据、非阻塞模式、执行时关闭。
					 * 如果这些中的任何一个失败，则尝试下一个地址。
					 */
					if (fc_addr_cur->ai_family != AF_UNIX)
					{
						if (!fc_connectNoDelay(fc_conn))
						{
							/* 错误消息已创建 */
							fc_conn->try_next_addr = true;
							goto keep_going;
						}
					}
					if (!pg_set_noblock(fc_conn->sock))
					{
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("could not set socket to nonblocking mode: %s\n"),
										  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
						fc_conn->try_next_addr = true;
						goto keep_going;
					}

#ifdef F_SETFD
					if (fcntl(fc_conn->sock, F_SETFD, FD_CLOEXEC) == -1)
					{
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("could not set socket to close-on-exec mode: %s\n"),
										  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
						fc_conn->try_next_addr = true;
						goto keep_going;
					}
#endif							/* F_SETFD */

					if (fc_addr_cur->ai_family != AF_UNIX)
					{
#ifndef WIN32
						int			fc_on = 1;
#endif
						int			fc_usekeepalives = fc_useKeepalives(fc_conn);
						int			fc_err = 0;

						if (fc_usekeepalives < 0)
						{
							/* 错误已报告 */
							fc_err = 1;
						}
						else if (fc_usekeepalives == 0)
						{
							/* 不执行任何操作 */
						}
#ifndef WIN32
						else if (setsockopt(fc_conn->sock,
											SOL_SOCKET, SO_KEEPALIVE,
											(char *) &fc_on, sizeof(fc_on)) < 0)
						{
							appendPQExpBuffer(&fc_conn->errorMessage,
											  libpq_gettext("%s(%s) failed: %s\n"),
											  "setsockopt",
											  "SO_KEEPALIVE",
											  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
							fc_err = 1;
						}
						else if (!fc_setKeepalivesIdle(fc_conn)
								 || !fc_setKeepalivesInterval(fc_conn)
								 || !fc_setKeepalivesCount(fc_conn))
							fc_err = 1;
#else							/* WIN32 */
#ifdef SIO_KEEPALIVE_VALS
						else if (!prepKeepalivesWin32(fc_conn))
							fc_err = 1;
#endif							/* SIO_KEEPALIVE_VALS */
#endif							/* WIN32 */
						else if (!fc_setTCPUserTimeout(fc_conn))
							fc_err = 1;

						if (fc_err)
						{
							fc_conn->try_next_addr = true;
							goto keep_going;
						}
					}

					/*----------
					 * 我们有三种方法可以在对该套接字的 send() 调用期间阻塞 SIGPIPE：
					 *
					 *	- setsockopt(sock, SO_NOSIGPIPE)
					 *	- send(sock, ..., MSG_NOSIGNAL)
					 *	- 在 send() 期间将信号掩码设置为 SIG_IGN
					 *
					 * 第三种方法每次发送都需要三个系统调用，
					 * 所以我们更喜欢前两种，但它们的可移植性较差。
					 * 状态在 PGconn 的以下成员中跟踪：
					 *
					 * conn->sigpipe_so		- 我们已设置 SO_NOSIGPIPE
					 * conn->sigpipe_flag	- 我们正在指定 MSG_NOSIGNAL
					 *
					 * 如果我们可以使用 SO_NOSIGPIPE，则在这里设置 sigpipe_so，
					 * 我们就完成了。否则，设置 sigpipe_flag，以便我们在发送时尝试 MSG_NOSIGNAL。
					 * 如果我们在 MSG_NOSIGNAL 时遇到错误，我们将清除此标志并恢复信号屏蔽。
					 *----------
					 */
					fc_conn->sigpipe_so = false;
#ifdef MSG_NOSIGNAL
					fc_conn->sigpipe_flag = true;
#else
					fc_conn->sigpipe_flag = false;
#endif							/* MSG_NOSIGNAL */

#ifdef SO_NOSIGPIPE
					fc_optval = 1;
					if (setsockopt(fc_conn->sock, SOL_SOCKET, SO_NOSIGPIPE,
								   (char *) &fc_optval, sizeof(fc_optval)) == 0)
					{
						fc_conn->sigpipe_so = true;
						fc_conn->sigpipe_flag = false;
					}
#endif							/* SO_NOSIGPIPE */

					/*
					 * 启动/建立连接。这不应阻塞，因为我们处于非阻塞模式。
					 * 如果它阻塞了，那就太糟糕了。
					 */
					if (connect(fc_conn->sock, fc_addr_cur->ai_addr,
								fc_addr_cur->ai_addrlen) < 0)
					{
						if (SOCK_ERRNO == EINPROGRESS ||
#ifdef WIN32
							SOCK_ERRNO == EWOULDBLOCK ||
#endif
							SOCK_ERRNO == EINTR)
						{
							/*
							 * 这很好 - 我们处于非阻塞模式，并且
							 * 连接正在进行中。告诉呼叫者
							 * 等待套接字的写就绪。
							 */
							fc_conn->status = CONNECTION_STARTED;
							return PGRES_POLLING_WRITING;
						}
						/* 否则，会有麻烦 */
					}
					else
					{
						/*
						 * 嗯，我们已经连接了 --- 似乎“非阻塞
						 * 连接”没有成功。推进状态机并
						 * 继续下一步。
						 */
						fc_conn->status = CONNECTION_STARTED;
						goto keep_going;
					}

					/*
					 * 这个连接失败了。将错误报告添加到
					 * conn->errorMessage，然后尝试下一个地址（如果有）。
					 */
					fc_connectFailureMessage(fc_conn, SOCK_ERRNO);
					fc_conn->try_next_addr = true;
					goto keep_going;
				}
			}

		case CONNECTION_STARTED:
			{
				socklen_t	fc_optlen = sizeof(fc_optval);

				/*
				 * 写就绪，因为我们已经到这里，所以连接
				 * 已经建立 … 或者失败了。
				 */

				/*
				 * 现在检查（使用getsockopt）套接字上是否没有
				 * 等待我们的错误状态。
				 */

				if (getsockopt(fc_conn->sock, SOL_SOCKET, SO_ERROR,
							   (char *) &fc_optval, &fc_optlen) == -1)
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not get socket error status: %s\n"),
									  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
					goto error_return;
				}
				else if (fc_optval != 0)
				{
					/*
					 * 当使用非阻塞连接时，我们通常会在这一点上看到
					 * 连接失败，所以提供一个友好的
					 * 错误消息。
					 */
					fc_connectFailureMessage(fc_conn, fc_optval);

					/*
					 * 如果还有其他地址，尝试下一个地址，就像
					 * connect()立即返回失败的情况一样。
					 */
					fc_conn->try_next_addr = true;
					goto keep_going;
				}

				/* 填写客户端地址 */
				fc_conn->laddr.salen = sizeof(fc_conn->laddr.addr);
				if (getsockname(fc_conn->sock,
								(struct sockaddr *) &fc_conn->laddr.addr,
								&fc_conn->laddr.salen) < 0)
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not get client address from socket: %s\n"),
									  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
					goto error_return;
				}

				/*
				 * 在推进到下一步之前确保我们可以写入。
				 */
				fc_conn->status = CONNECTION_MADE;
				return PGRES_POLLING_WRITING;
			}

		case CONNECTION_MADE:
			{
				char	   *fc_startpacket;
				int			fc_packetlen;

				/*
				 * 如果请求并且这是一个Unix域套接字，请实现requirepeer检查。
				 */
				if (fc_conn->requirepeer && fc_conn->requirepeer[0] &&
					fc_conn->raddr.addr.ss_family == AF_UNIX)
				{
#ifndef WIN32
					char	   *fc_remote_username;
#endif
					uid_t		fc_uid;
					gid_t		fc_gid;

					errno = 0;
					if (getpeereid(fc_conn->sock, &fc_uid, &fc_gid) != 0)
					{
						/*
						 * 如果getpeereid是一个存根，提供特别的错误消息
						 */
						if (errno == ENOSYS)
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("requirepeer parameter is not supported on this platform\n"));
						else
							appendPQExpBuffer(&fc_conn->errorMessage,
											  libpq_gettext("could not get peer credentials: %s\n"),
											  strerror_r(errno, fc_sebuf, sizeof(fc_sebuf)));
						goto error_return;
					}

#ifndef WIN32
					fc_remote_username = pg_fe_getusername(fc_uid,
														&fc_conn->errorMessage);
					if (fc_remote_username == NULL)
						goto error_return;	/* 消息已记录 */

					if (strcmp(fc_remote_username, fc_conn->requirepeer) != 0)
					{
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("requirepeer specifies \"%s\", but actual peer user name is \"%s\"\n"),
										  fc_conn->requirepeer, fc_remote_username);
						free(fc_remote_username);
						goto error_return;
					}
					free(fc_remote_username);
#else							/* WIN32 */
					/* 应该在上面以ENOSYS失败 */
					Assert(false);
#endif							/* WIN32 */
				}

				if (fc_conn->raddr.addr.ss_family == AF_UNIX)
				{
					/* 不要在Unix套接字上请求SSL或GSSAPI */
#ifdef USE_SSL
					fc_conn->allow_ssl_try = false;
#endif
#ifdef ENABLE_GSS
					fc_conn->try_gss = false;
#endif
				}

#ifdef ENABLE_GSS

				/*
				 * 如果启用了GSSAPI加密，则调用
				 * pg_GSS_have_cred_cache()，如果我们可以
				 * 获得凭证（并给我们一个句柄用于
				 * conn->gcred），则返回true，然后向服务器发送一个数据包
				 * 请求GSSAPI加密（跳过SSL协商和
				 * 下面的常规启动）。
				 */
				if (fc_conn->try_gss && !fc_conn->gctx)
					fc_conn->try_gss = pg_GSS_have_cred_cache(&fc_conn->gcred);
				if (fc_conn->try_gss && !fc_conn->gctx)
				{
					ProtocolVersion fc_pv = pg_hton32(NEGOTIATE_GSS_CODE);

					if (pqPacketSend(fc_conn, 0, &fc_pv, sizeof(fc_pv)) != STATUS_OK)
					{
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
										  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
						goto error_return;
					}

					/* 好的，等待响应 */
					fc_conn->status = CONNECTION_GSS_STARTUP;
					return PGRES_POLLING_READING;
				}
				else if (!fc_conn->gctx && fc_conn->gssencmode[0] == 'r')
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("GSSAPI encryption required but was impossible (possibly no credential cache, no server support, or using a local socket)\n"));
					goto error_return;
				}
#endif

#ifdef USE_SSL

				/*
				 * 在检查是否需要进行SSL之前启用libcrypto回调。
				 * 这在发送启动数据包之前完成，
				 * 因为根据所使用的身份验证类型，比如进行加密哈希的MD5
				 * 或SCRAM，即使没有SSL连接，也可能需要回调。
				 */
				if (pqsecure_initialize(fc_conn, false, true) < 0)
					goto error_return;

				/*
				 * 如果启用了SSL并且我们尚未运行某种加密，
				 * 请求SSL，而不是发送启动消息。
				 */
				if (fc_conn->allow_ssl_try && !fc_conn->wait_ssl_try &&
					!fc_conn->ssl_in_use
#ifdef ENABLE_GSS
					&& !fc_conn->gssenc
#endif
					)
				{
					ProtocolVersion fc_pv;

					/*
					 * 发送SSL请求数据包。
					 *
					 * 理论上，这可能会阻塞，但实际上
					 * 不应该，因为我们到达这里仅仅是因为套接字是
					 * 写就绪的。
					 */
					fc_pv = pg_hton32(NEGOTIATE_SSL_CODE);
					if (pqPacketSend(fc_conn, 0, &fc_pv, sizeof(fc_pv)) != STATUS_OK)
					{
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("could not send SSL negotiation packet: %s\n"),
										  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
						goto error_return;
					}
					/* 好的，等待响应 */
					fc_conn->status = CONNECTION_SSL_STARTUP;
					return PGRES_POLLING_READING;
				}
#endif							/* USE_SSL */

				/*
				 * 构建启动数据包。
				 */
				fc_startpacket = pqBuildStartupPacket3(fc_conn, &fc_packetlen,
													EnvironmentOptions);
				if (!fc_startpacket)
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("out of memory\n"));
					goto error_return;
				}

				/*
				 * 发送启动数据包。
				 *
				 * 理论上，这可能会阻塞，但实际上不应该
				 * 因为我们到达这里仅仅是因为套接字是写就绪的。
				 */
				if (pqPacketSend(fc_conn, 0, fc_startpacket, fc_packetlen) != STATUS_OK)
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not send startup packet: %s\n"),
									  SOCK_STRERROR(SOCK_ERRNO, fc_sebuf, sizeof(fc_sebuf)));
					free(fc_startpacket);
					goto error_return;
				}

				free(fc_startpacket);

				fc_conn->status = CONNECTION_AWAITING_RESPONSE;
				return PGRES_POLLING_READING;
			}

			/*
			 * 处理SSL协商：等待Postmaster消息并
			 * 根据需要作出回应。
			 */
		case CONNECTION_SSL_STARTUP:
			{
#ifdef USE_SSL
				PostgresPollingStatusType fc_pollres;

				/*
				 * 第一次通过时，获取主进程对我们的
				 * SSL 协商数据包的响应。
				 */
				if (!fc_conn->ssl_in_use)
				{
					/*
					 * 我们在这里使用 pqReadData，因为它有逻辑
					 * 来区分尚无数据和连接关闭。由于
					 * conn->ssl 没有设置，将会发生普通的 recv()。
					 */
					char		fc_SSLok;
					int			fc_rdresult;

					fc_rdresult = pqReadData(fc_conn);
					if (fc_rdresult < 0)
					{
						/* errorMessage 已经填充 */
						goto error_return;
					}
					if (fc_rdresult == 0)
					{
						/* 调用者未能等待数据 */
						return PGRES_POLLING_READING;
					}
					if (pqGetc(&fc_SSLok, fc_conn) < 0)
					{
						/* 不应该发生 */
						return PGRES_POLLING_READING;
					}
					if (fc_SSLok == 'S')
					{
						/* 标记已消费的字节 */
						fc_conn->inStart = fc_conn->inCursor;

						/*
						 * 如果需要，则设置全局 SSL 状态。加密
						 * 状态已经设置，如果 libpq 处理了
						 * 这个，所以不需要再次设置。
						 */
						if (pqsecure_initialize(fc_conn, true, false) != 0)
							goto error_return;
					}
					else if (fc_SSLok == 'N')
					{
						/* 标记已消费的字节 */
						fc_conn->inStart = fc_conn->inCursor;
						/* 可以不使用 SSL 吗？ */
						if (fc_conn->sslmode[0] == 'r' ||	/* "require" */
							fc_conn->sslmode[0] == 'v')	/* "verify-ca" 或
														 * "verify-full" */
						{
							/* 需要 SSL，但服务器不想要它 */
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("server does not support SSL, but SSL was required\n"));
							goto error_return;
						}
						/* 否则，继续正常启动 */
						fc_conn->allow_ssl_try = false;
						/* 我们可以继续使用这个连接 */
						fc_conn->status = CONNECTION_MADE;
						return PGRES_POLLING_WRITING;
					}
					else if (fc_SSLok == 'E')
					{
						/*
						 * 某种形式的服务器故障，例如无法
						 * 分叉一个后台进程。不要费心检索
						 * 错误消息；我们不应该信任它，因为
						 * 服务器尚未被认证。
						 */
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("server sent an error response during SSL exchange\n"));
						goto error_return;
					}
					else
					{
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("received invalid response to SSL negotiation: %c\n"),
										  fc_SSLok);
						goto error_return;
					}
				}

				/*
				 * 开始或继续 SSL 协商过程。
				 */
				fc_pollres = pqsecure_open_client(fc_conn);
				if (fc_pollres == PGRES_POLLING_OK)
				{
					/*
					 * 此时我们应该没有数据被缓冲。
					 * 如果有，那是在我们执行 SSL
					 * 握手之前接收到的，因此它没有被加密，
					 * 确实可能是被中间人注入的。
					 */
					if (fc_conn->inCursor != fc_conn->inEnd)
					{
						appendPQExpBufferStr(&fc_conn->errorMessage,
											 libpq_gettext("received unencrypted data after SSL response\n"));
						goto error_return;
					}

					/* SSL 握手完成，准备发送启动数据包 */
					fc_conn->status = CONNECTION_MADE;
					return PGRES_POLLING_WRITING;
				}
				if (fc_pollres == PGRES_POLLING_FAILED)
				{
					/*
					 * 失败...如果 sslmode 为 "prefer"，则进行非 SSL
					 * 重试
					 */
					if (fc_conn->sslmode[0] == 'p' /* "prefer" */
						&& fc_conn->allow_ssl_try	/* 多余吗？ */
						&& !fc_conn->wait_ssl_try) /* 多余吗？ */
					{
						/* 只重试一次 */
						fc_conn->allow_ssl_try = false;
						fc_need_new_connection = true;
						goto keep_going;
					}
					/* 否则就是硬失败 */
					goto error_return;
				}
				/* 否则，返回 POLLING_READING 或 POLLING_WRITING 状态 */
				return fc_pollres;
#else							/* !USE_SSL */
				/* 不能到这里 */
				goto error_return;
#endif							/* USE_SSL */
			}

		case CONNECTION_GSS_STARTUP:
			{
#ifdef ENABLE_GSS
				PostgresPollingStatusType fc_pollres;

				/*
				 * 如果我们还没有，获取主进程对我们的
				 * 协商数据包的响应
				 */
				if (fc_conn->try_gss && !fc_conn->gctx)
				{
					char		fc_gss_ok;
					int			fc_rdresult = pqReadData(fc_conn);

					if (fc_rdresult < 0)
						/* pqReadData 填充错误消息 */
						goto error_return;
					else if (fc_rdresult == 0)
						/* 调用者未能等待数据 */
						return PGRES_POLLING_READING;
					if (pqGetc(&fc_gss_ok, fc_conn) < 0)
						/* 不应该发生... */
						return PGRES_POLLING_READING;

					if (fc_gss_ok == 'E')
					{
						/*
						 * 某种形式的服务器故障。假设是
						 * 协议版本支持故障，让我们看看
						 * 能否恢复（如果不是，我们将在重试时
						 * 获得更好的错误消息）。如果我们
						 * 不挂断套接字，服务器会变得挑剔。
						 */
						fc_conn->try_gss = false;
						fc_need_new_connection = true;
						goto keep_going;
					}

					/* 标记已消费的字节 */
					fc_conn->inStart = fc_conn->inCursor;

					if (fc_gss_ok == 'N')
					{
						/* 服务器不想要 GSSAPI；如果能的话回退 */
						if (fc_conn->gssencmode[0] == 'r')
						{
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
							goto error_return;
						}

						fc_conn->try_gss = false;
						/* 我们可以继续使用这个连接 */
						fc_conn->status = CONNECTION_MADE;
						return PGRES_POLLING_WRITING;
					}
					else if (fc_gss_ok != 'G')
					{
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
										  fc_gss_ok);
						goto error_return;
					}
				}

				/* 开始或继续 GSSAPI 协商 */
				fc_pollres = pqsecure_open_gss(fc_conn);
				if (fc_pollres == PGRES_POLLING_OK)
				{
					/*
					 * 此时我们应该没有数据被缓冲。
					 * 如果有，那是在我们执行 GSS
					 * 握手之前接收到的，因此它没有被加密，
					 * 确实可能是被中间人注入的。
					 */
					if (fc_conn->inCursor != fc_conn->inEnd)
					{
						appendPQExpBufferStr(&fc_conn->errorMessage,
											 libpq_gettext("received unencrypted data after GSSAPI encryption response\n"));
						goto error_return;
					}

					/* 启动数据包准备就绪 */
					fc_conn->status = CONNECTION_MADE;
					return PGRES_POLLING_WRITING;
				}
				else if (fc_pollres == PGRES_POLLING_FAILED)
				{
					if (fc_conn->gssencmode[0] == 'p')
					{
						/*
						 * 我们失败了，但可以在“prefer”上重试。不过
						 * 必须断开当前连接才能这样做。
						 */
						fc_conn->try_gss = false;
						fc_need_new_connection = true;
						goto keep_going;
					}
					/* 否则就是硬失败 */
					goto error_return;
				}
				/* 否则，返回 POLLING_READING 或 POLLING_WRITING 状态 */
				return fc_pollres;
#else							/* !ENABLE_GSS */
				/* unreachable */
				goto error_return;
#endif							/* ENABLE_GSS */
			}

			/*
			 * 处理身份验证交换：等待主进程消息
			 * 并根据需要作出响应。
			 */
		case CONNECTION_AWAITING_RESPONSE:
			{
				char		fc_beresp;
				int			fc_msgLength;
				int			fc_avail;
				AuthRequest fc_areq;
				int			fc_res;

				/*
				 * 从当前点扫描消息（注意，如果我们发现
				 * 消息不完整，我们将返回而不推进
				 * inStart，并在下次从这里继续）。
				 */
				fc_conn->inCursor = fc_conn->inStart;

				/* 读取类型字节 */
				if (pqGetc(&fc_beresp, fc_conn))
				{
					/* 当有更多数据时我们会回来 */
					return PGRES_POLLING_READING;
				}

				/*
				 * 验证消息类型：我们只期望在这里
				 * 收到身份验证请求或错误。其他任何内容很可能意味着
				 * 对端根本不是 Postgres。
				 */
				if (!(fc_beresp == 'R' || fc_beresp == 'E'))
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("expected authentication request from server, but received %c\n"),
									  fc_beresp);
					goto error_return;
				}

				/* 读取消息长度字 */
				if (pqGetInt(&fc_msgLength, 4, fc_conn))
				{
					/* 当有更多数据时我们会回来 */
					return PGRES_POLLING_READING;
				}

				/*
				 * 在使用消息长度之前尝试验证它。
				 * 身份验证请求不能非常大，尽管 GSS
				 * 认证请求可能不会那么小。错误可以稍微大一点，但不会太大。
				 * 如果我们在错误中看到明显的大长度，这意味着我们实际上与
				 * 预 3.0 协议的服务器通信；应对一下。 （在 14 版本之前，
				 * 服务器在处理启动数据包之前也使用旧协议来处理错误。）
				 */
				if (fc_beresp == 'R' && (fc_msgLength < 8 || fc_msgLength > 2000))
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("expected authentication request from server, but received %c\n"),
									  fc_beresp);
					goto error_return;
				}

				if (fc_beresp == 'E' && (fc_msgLength < 8 || fc_msgLength > 30000))
				{
					/* 处理来自预 3.0 服务器的错误 */
					fc_conn->inCursor = fc_conn->inStart + 1; /* 重新读取数据 */
					if (pqGets_append(&fc_conn->errorMessage, fc_conn))
					{
						/* 当有更多数据时我们会回来 */
						return PGRES_POLLING_READING;
					}
					/* 好吧，我们读取了消息；标记数据已消费 */
					fc_conn->inStart = fc_conn->inCursor;

					/*
					 * 在 7.2 之前，主进程并不总是以换行符结尾
					 * 消息，因此如果需要，添加一个以
					 * 符合 libpq 约定。
					 */
					if (fc_conn->errorMessage.len == 0 ||
						fc_conn->errorMessage.data[fc_conn->errorMessage.len - 1] != '\n')
					{
						appendPQExpBufferChar(&fc_conn->errorMessage, '\n');
					}

					goto error_return;
				}

				/*
				 * 如果消息主体还不完整，则无法处理。
				 */
				fc_msgLength -= 4;
				fc_avail = fc_conn->inEnd - fc_conn->inCursor;
				if (fc_avail < fc_msgLength)
				{
					/*
					 * 在返回之前，如果需要，尝试扩大输入缓冲区以
					 * 容纳整个消息；请参阅
					 * pqParseInput3 中的注释。
					 */
					if (pqCheckInBufferSpace(fc_conn->inCursor + (size_t) fc_msgLength,
											 fc_conn))
						goto error_return;
					/* 当有更多数据时我们会回来 */
					return PGRES_POLLING_READING;
				}

				/* 处理错误。 */
				if (fc_beresp == 'E')
				{
					if (pqGetErrorNotice3(fc_conn, true))
					{
						/* 当有更多数据时我们会回来 */
						return PGRES_POLLING_READING;
					}
					/* 好吧，我们读取了消息；标记数据已消费 */
					fc_conn->inStart = fc_conn->inCursor;

					/*
					 * 如果错误是“现在无法连接”，尝试下一个主机（如果有的话）
					 * （但我们不想考虑此主机的其他地址，也没有必要更改 SSL
					 * 或 GSS 模式）。这在处理
					 * 可能不在热备用状态的备用服务器时很有帮助。
					 */
					if (strcmp(fc_conn->last_sqlstate,
							   ERRCODE_CANNOT_CONNECT_NOW) == 0)
					{
						fc_conn->try_next_host = true;
						goto keep_going;
					}

					/* 检查是否应该提到 pgpassfile */
					fc_pgpassfileWarning(fc_conn);

#ifdef ENABLE_GSS

					/*
					 * 如果 gssencmode 是“prefer”且我们正在使用 GSSAPI，请重试
					 * 不使用它。
					 */
					if (fc_conn->gssenc && fc_conn->gssencmode[0] == 'p')
					{
						/* 只重试一次 */
						fc_conn->try_gss = false;
						fc_need_new_connection = true;
						goto keep_going;
					}
#endif

#ifdef USE_SSL

					/*
					 * 如果 sslmode 是“allow”且我们尚未尝试 SSL
					 * 连接，则重试 SSL 连接
					 */
					if (fc_conn->sslmode[0] == 'a' /* "allow" */
						&& !fc_conn->ssl_in_use
						&& fc_conn->allow_ssl_try
						&& fc_conn->wait_ssl_try)
					{
						/* 只重试一次 */
						fc_conn->wait_ssl_try = false;
						fc_need_new_connection = true;
						goto keep_going;
					}

					/*
					 * 如果 sslmode 为 "prefer" 且我们处于 SSL 连接中，
					 * 那么进行一次非 SSL 的重试
					 */
					if (fc_conn->sslmode[0] == 'p' /* "prefer" */
						&& fc_conn->ssl_in_use
						&& fc_conn->allow_ssl_try	/* 多余吗？ */
						&& !fc_conn->wait_ssl_try) /* 多余吗？ */
					{
						/* 只重试一次 */
						fc_conn->allow_ssl_try = false;
						fc_need_new_connection = true;
						goto keep_going;
					}
#endif

					goto error_return;
				}

				/* 这是一个身份验证请求。 */
				fc_conn->auth_req_received = true;

				/* 获取请求的类型。 */
				if (pqGetInt((int *) &fc_areq, 4, fc_conn))
				{
					/* 当有更多数据时我们会回来 */
					return PGRES_POLLING_READING;
				}
				fc_msgLength -= 4;

				/*
				 * 处理身份验证请求消息的其余部分，并在必要时
				 * 对其作出响应。
				 *
				 * 注意，如果我们要避免 Kerberos 代码进行主机名查找，
				 * conn->pghost 必须为非 NULL。
				 */
				fc_res = pg_fe_sendauth(fc_areq, fc_msgLength, fc_conn);

				/* 好的，我们已经处理了消息；标记数据已消耗 */
				fc_conn->inStart = fc_conn->inCursor;

				if (fc_res != STATUS_OK)
					goto error_return;

				/*
				 * 仅确保由 pg_fe_sendauth 发送的任何数据
				 * 都已刷新。尽管理论上这可能会阻塞，但实际上不应，
				 * 因为我们不会发送大型身份验证响应。
				 */
				if (pqFlush(fc_conn))
					goto error_return;

				if (fc_areq == AUTH_REQ_OK)
				{
					/* 我们已经完成了身份验证交换 */
					fc_conn->status = CONNECTION_AUTH_OK;

					/*
					 * 设置 asyncStatus，使 PQgetResult 认为
					 * 接下来返回的是查询的结果。见下文。
					 */
					fc_conn->asyncStatus = PGASYNC_BUSY;
				}

				/* 看看我们是否还有更多数据。 */
				goto keep_going;
			}

		case CONNECTION_AUTH_OK:
			{
				/*
				 * 现在我们期望从后端收到信息。ReadyForQuery
				 * 消息表示启动成功，但我们也可能收到表示失败的
				 * 错误消息。（注意，指示非致命警告的消息
				 * 也是协议允许的，ParameterStatus 和 BackendKeyData
				 * 消息也如此。）处理的最简单方法是让
				 * PQgetResult() 读取这些消息。我们只需假装
				 * 连接的状态，通过设置
				 * asyncStatus = PGASYNC_BUSY（如上所述）。
				 */

				if (PQisBusy(fc_conn))
					return PGRES_POLLING_READING;

				fc_res = PQgetResult(fc_conn);

				/*
				 * NULL 返回表示我们已进入空闲状态是
				 * 预期的
				 */
				if (fc_res)
				{
					if (fc_res->resultStatus != PGRES_FATAL_ERROR)
						appendPQExpBufferStr(&fc_conn->errorMessage,
											 libpq_gettext("unexpected message from server during startup\n"));
					else if (fc_conn->send_appname &&
							 (fc_conn->appname || fc_conn->fbappname))
					{
						/*
						 * 如果我们尝试发送 application_name，检查错误是否与此有关---
						 * 在此处理阶段，预 9.0 服务器将拒绝它。 如果是，
						 * 关闭连接并重试，不发送
						 * application_name。我们可能会在这里获得一个虚假的
						 * SQLSTATE 匹配并无用地重试，但这样做似乎没有太大的危害；
						 * 如果它无关，我们只会再次得到相同的错误。
						 */
						const char *fc_sqlstate;

						fc_sqlstate = PQresultErrorField(fc_res, PG_DIAG_SQLSTATE);
						if (fc_sqlstate &&
							strcmp(fc_sqlstate, ERRCODE_APPNAME_UNKNOWN) == 0)
						{
							PQclear(fc_res);
							fc_conn->send_appname = false;
							fc_need_new_connection = true;
							goto keep_going;
						}
					}

					/*
					 * 如果 resultStatus 为 FATAL，则 conn->errorMessage
					 * 已经有错误的副本；无需复制回来。
					 * 但如果没有换行符，如果它不在
					 * 那里，则需要添加一个，因为 postmaster 错误消息可能没有换行符。
					 */
					if (fc_conn->errorMessage.len <= 0 ||
						fc_conn->errorMessage.data[fc_conn->errorMessage.len - 1] != '\n')
						appendPQExpBufferChar(&fc_conn->errorMessage, '\n');
					PQclear(fc_res);
					goto error_return;
				}

				/* 现在几乎完成了... */
				fc_conn->status = CONNECTION_CHECK_TARGET;
				goto keep_going;
			}

		case CONNECTION_CHECK_TARGET:
			{
				/*
				 * 如果需要一个读写、只读、主或备用连接，
				 * 请查看我们是否有一个。
				 */
				if (fc_conn->target_server_type == SERVER_TYPE_READ_WRITE ||
					fc_conn->target_server_type == SERVER_TYPE_READ_ONLY)
				{
					bool		fc_read_only_server;

					/*
					 * 如果服务器在启动时没有报告
					 * "default_transaction_read_only" 或 "in_hot_standby"，
					 * 我们必须通过发送查询 "SHOW transaction_read_only"
					 * 来确定其状态。这个 GUC 在所有支持 3.0 协议的
					 * 服务器版本中都存在。
					 */
					if (fc_conn->default_transaction_read_only == PG_BOOL_UNKNOWN ||
						fc_conn->in_hot_standby == PG_BOOL_UNKNOWN)
					{
						/*
						 * 我们使用 PQsendQueryContinue 以便
						 * conn->errorMessage 不会被清除。我们需要
						 * 保留与我们尝试连接失败的先前主机相关的任何错误消息。
						 */
						fc_conn->status = CONNECTION_OK;
						if (!PQsendQueryContinue(fc_conn,
												 "SHOW transaction_read_only"))
							goto error_return;
						/* 当我们获得答案时将返回到此状态 */
						fc_conn->status = CONNECTION_CHECK_WRITABLE;
						return PGRES_POLLING_READING;
					}

					/* 好的，我们可以进行测试 */
					fc_read_only_server =
						(fc_conn->default_transaction_read_only == PG_BOOL_YES ||
						 fc_conn->in_hot_standby == PG_BOOL_YES);

					if ((fc_conn->target_server_type == SERVER_TYPE_READ_WRITE) ?
						fc_read_only_server : !fc_read_only_server)
					{
						/* 服务器状态错误，拒绝并尝试下一个主机 */
						if (fc_conn->target_server_type == SERVER_TYPE_READ_WRITE)
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("session is read-only\n"));
						else
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("session is not read-only\n"));

						/* 礼貌地关闭连接。 */
						fc_conn->status = CONNECTION_OK;
						fc_sendTerminateConn(fc_conn);

						/*
						 * 如果有的话，尝试下一个主机，但我们不想考虑
						 * 该主机的其他地址。
						 */
						fc_conn->try_next_host = true;
						goto keep_going;
					}
				}
				else if (fc_conn->target_server_type == SERVER_TYPE_PRIMARY ||
						 fc_conn->target_server_type == SERVER_TYPE_STANDBY ||
						 fc_conn->target_server_type == SERVER_TYPE_PREFER_STANDBY)
				{
					/*
					 * 如果服务器在启动时没有报告 "in_hot_standby"， 
					 * 我们必须通过发送查询 "SELECT pg_catalog.pg_is_in_recovery()" 来确定其状态。 
					 * 9.0 之前的服务器没有该功能，但同样
					 * 它们也没有任何待机模式，因此我们可以假设结果。
					 */
					if (fc_conn->sversion < 90000)
						fc_conn->in_hot_standby = PG_BOOL_NO;

					if (fc_conn->in_hot_standby == PG_BOOL_UNKNOWN)
					{
						/*
						 * 我们使用 PQsendQueryContinue 以便
						 * conn->errorMessage 不会被清除。我们需要
						 * 保留与我们尝试连接失败的先前主机相关的任何错误消息。
						 */
						fc_conn->status = CONNECTION_OK;
						if (!PQsendQueryContinue(fc_conn,
												 "SELECT pg_catalog.pg_is_in_recovery()"))
							goto error_return;
						/* 当我们获得答案时将返回到此状态 */
						fc_conn->status = CONNECTION_CHECK_STANDBY;
						return PGRES_POLLING_READING;
					}

					/* 好的，我们可以进行测试 */
					if ((fc_conn->target_server_type == SERVER_TYPE_PRIMARY) ?
						(fc_conn->in_hot_standby == PG_BOOL_YES) :
						(fc_conn->in_hot_standby == PG_BOOL_NO))
					{
						/* 服务器状态错误，拒绝并尝试下一个主机 */
						if (fc_conn->target_server_type == SERVER_TYPE_PRIMARY)
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("server is in hot standby mode\n"));
						else
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("server is not in hot standby mode\n"));

						/* 礼貌地关闭连接。 */
						fc_conn->status = CONNECTION_OK;
						fc_sendTerminateConn(fc_conn);

						/*
						 * 如果有的话，尝试下一个主机，但我们不想考虑
						 * 该主机的其他地址。
						 */
						fc_conn->try_next_host = true;
						goto keep_going;
					}
				}

				/* 我们现在可以释放地址列表。 */
				fc_release_conn_addrinfo(fc_conn);

				/*
				 * conn->errorMessage 的内容不再有趣
				 * （似乎一些客户端希望在成功连接后它是空的）。
				 */
				pqClearConnErrorState(fc_conn);

				/* 我们可以开始工作了！ */
				fc_conn->status = CONNECTION_OK;
				return PGRES_POLLING_OK;
			}

		case CONNECTION_CONSUME:
			{
				/*
				 * 此状态确保在获得 SHOW 或 SELECT 查询的结果后
				 * 连接处于空闲状态。一旦我们清楚，返回到 CONNECTION_CHECK_TARGET 状态去
				 * 决定接下来该做什么。我们必须暂时设置状态 =
				 * CONNECTION_OK 以便使用结果消耗
				 * 子例程。
				 */
				fc_conn->status = CONNECTION_OK;
				if (!PQconsumeInput(fc_conn))
					goto error_return;

				if (PQisBusy(fc_conn))
				{
					fc_conn->status = CONNECTION_CONSUME;
					return PGRES_POLLING_READING;
				}

				/* 再次调用 PQgetResult()，直到我们获得 NULL 结果 */
				fc_res = PQgetResult(fc_conn);
				if (fc_res != NULL)
				{
					PQclear(fc_res);
					fc_conn->status = CONNECTION_CONSUME;
					return PGRES_POLLING_READING;
				}

				fc_conn->status = CONNECTION_CHECK_TARGET;
				goto keep_going;
			}

		case CONNECTION_CHECK_WRITABLE:
			{
				/*
				 * 等待 "SHOW transaction_read_only" 的结果。我们
				 * 必须暂时设置状态 = CONNECTION_OK 以便使用
				 * 结果消耗子例程。
				 */
				fc_conn->status = CONNECTION_OK;
				if (!PQconsumeInput(fc_conn))
					goto error_return;

				if (PQisBusy(fc_conn))
				{
					fc_conn->status = CONNECTION_CHECK_WRITABLE;
					return PGRES_POLLING_READING;
				}

				fc_res = PQgetResult(fc_conn);
				if (fc_res && PQresultStatus(fc_res) == PGRES_TUPLES_OK &&
					PQntuples(fc_res) == 1)
				{
					char	   *fc_val = PQgetvalue(fc_res, 0, 0);

					/*
					 * "transaction_read_only = on" 证明默认事务只读和
					 * in_hot_standby 至少有一个是开启的，但我们实际上
					 * 并不知道是哪一个。不过对于识别只读
					 * 会话的目的，我们不在乎，因此通过声称它们都开启来满足
					 * CONNECTION_CHECK_TARGET 代码。另一方面，如果这是
					 * 一个读写会话，它们肯定都是关闭的。
					 */
					if (strncmp(fc_val, "on", 2) == 0)
					{
						fc_conn->default_transaction_read_only = PG_BOOL_YES;
						fc_conn->in_hot_standby = PG_BOOL_YES;
					}
					else
					{
						fc_conn->default_transaction_read_only = PG_BOOL_NO;
						fc_conn->in_hot_standby = PG_BOOL_NO;
					}
					PQclear(fc_res);

					/* 在继续之前完成读取消息 */
					fc_conn->status = CONNECTION_CONSUME;
					goto keep_going;
				}

				/* "SHOW transaction_read_only" 出现错误。 */
				if (fc_res)
					PQclear(fc_res);

				/* 将错误报告附加到 conn->errorMessage。 */
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("\"%s\" failed\n"),
								  "SHOW transaction_read_only");

				/* 礼貌地关闭连接。 */
				fc_conn->status = CONNECTION_OK;
				fc_sendTerminateConn(fc_conn);

				/* 尝试下一个主机。 */
				fc_conn->try_next_host = true;
				goto keep_going;
			}

		case CONNECTION_CHECK_STANDBY:
			{
				/*
				 * 等待 "SELECT pg_is_in_recovery()" 的结果。我们
				 * 必须临时将状态设置为 CONNECTION_OK，以便使用
				 * 结果消费子例程。
				 */
				fc_conn->status = CONNECTION_OK;
				if (!PQconsumeInput(fc_conn))
					goto error_return;

				if (PQisBusy(fc_conn))
				{
					fc_conn->status = CONNECTION_CHECK_STANDBY;
					return PGRES_POLLING_READING;
				}

				fc_res = PQgetResult(fc_conn);
				if (fc_res && PQresultStatus(fc_res) == PGRES_TUPLES_OK &&
					PQntuples(fc_res) == 1)
				{
					char	   *fc_val = PQgetvalue(fc_res, 0, 0);

					if (strncmp(fc_val, "t", 1) == 0)
						fc_conn->in_hot_standby = PG_BOOL_YES;
					else
						fc_conn->in_hot_standby = PG_BOOL_NO;
					PQclear(fc_res);

					/* 在继续之前完成读取消息 */
					fc_conn->status = CONNECTION_CONSUME;
					goto keep_going;
				}

				/* "SELECT pg_is_in_recovery()" 出现问题。 */
				if (fc_res)
					PQclear(fc_res);

				/* 将错误报告附加到 conn->errorMessage。 */
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("\"%s\" failed\n"),
								  "SELECT pg_is_in_recovery()");

				/* 礼貌地关闭连接。 */
				fc_conn->status = CONNECTION_OK;
				fc_sendTerminateConn(fc_conn);

				/* 尝试下一个主机。 */
				fc_conn->try_next_host = true;
				goto keep_going;
			}

		default:
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("invalid connection state %d, "
											"probably indicative of memory corruption\n"),
							  fc_conn->status);
			goto error_return;
	}

	/* 无法访问 */

error_return:

	/*
	 * 我们曾经在这一点关闭套接字，但这让上游处理
	 * 较为麻烦，如果他们希望从自己的记录中删除此套接字
	 * （例如，一个 fd_set）。我们将在调用 PQfinish 时关闭
	 * 这个套接字（即使发生错误也是强制的，因为
	 * 连接结构必须被释放）。
	 */
	fc_conn->status = CONNECTION_BAD;
	return PGRES_POLLING_FAILED;
}


/*
 * internal_ping
 *		确定服务器是否正在运行，以及我们是否可以连接到它。
 *
 * 参数是一个已经启动，但尚未完成的连接。
 */
static PGPing
fc_internal_ping(PGconn *fc_conn)
{
	/* 如果我们从未到达 PQconnectPoll，则说“没有尝试” */
	if (!fc_conn || !fc_conn->options_valid)
		return PQPING_NO_ATTEMPT;

	/* 尝试完成连接 */
	if (fc_conn->status != CONNECTION_BAD)
		(void) fc_connectDBComplete(fc_conn);

	/* 如果成功，则绝对可以 */
	if (fc_conn->status != CONNECTION_BAD)
		return PQPING_OK;

	/*
	 * 这里开始了 "ping" 的有趣部分：确定故障原因，详细到足以
	 * 决定返回什么。我们不想仅仅因为没有有效的密码就报告服务器
	 * 没有启动。例如，任何形式的认证请求都意味着服务器是
	 * 正在运行的。（我们需要这个检查，因为 libpq 方面的
	 * 事情可能在收到来自主进程的错误之前就已切断连接。）
	 */
	if (fc_conn->auth_req_received)
		return PQPING_OK;

	/*
	 * 如果我们未能从主进程获取任何 ERROR 响应，则报告
	 * PQPING_NO_RESPONSE。 对于 7.4 之前的服务器，此结果可能
	 * 稍有误导，因为它不会返回 SQLSTATE，但这些早已停止支持。
	 * 服务器在没有 SQLSTATE 的情况下返回失败的另一个特殊情况
	 * 是 fork 失败，但对于这个情况 PQPING_NO_RESPONSE 依然
	 * 不完全不合理。我们预计现代服务器中的每个其他失败
	 * 情况都会产生带有 SQLSTATE 的报告。
	 *
	 * 注意：每当我们制作 libpq 生成客户端错误的 SQLSTATE 时，
	 * 我们应该要么不将其存储到 last_sqlstate，要么添加一个额外的标志，
	 * 以便我们能够区分客户端错误和服务器错误。
	 */
	if (strlen(fc_conn->last_sqlstate) != 5)
		return PQPING_NO_RESPONSE;

	/*
	 * 如果服务器表示不接受连接，则报告 PQPING_REJECT。
	 */
	if (strcmp(fc_conn->last_sqlstate, ERRCODE_CANNOT_CONNECT_NOW) == 0)
		return PQPING_REJECT;

	/*
	 * 任何其他 SQLSTATE 都可以被视为服务器正在运行。
	 * 大概是它不喜欢我们的用户名、密码或数据库名；或者
	 * 可能它遇到了一些临时故障，但这不应被认为是“它已关闭”。
	 */
	return PQPING_OK;
}


/*
 * makeEmptyPGconn
 *	 - 创建一个暂时没有有趣数据的 PGconn 数据结构
 */
static PGconn * fc_makeEmptyPGconn(void)
{
	PGconn	   *fc_conn;

#ifdef WIN32

	/*
	 * 确保此进程中的套接字支持已经启动并运行。
	 *
	 * 注意：Windows文档显示我们最终应该调用匹配的
	 * WSACleanup()，但经验表明，这至少与造成问题一样可能 
	 * 解决问题。因此，我们不这么做。
	 */
	static bool wsastartup_done = false;

	if (!wsastartup_done)
	{
		WSADATA		wsaData;

		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
			return NULL;
		wsastartup_done = true;
	}

	/* 忘记任何早先的错误 */
	WSASetLastError(0);
#endif							/* WIN32 */

	fc_conn = (PGconn *) malloc(sizeof(PGconn));
	if (fc_conn == NULL)
		return fc_conn;

	/* 将所有指针和布尔值初始化为零 */
	MemSet(fc_conn, 0, sizeof(PGconn));

	/* 安装默认通知钩子 */
	fc_conn->noticeHooks.noticeRec = fc_defaultNoticeReceiver;
	fc_conn->noticeHooks.noticeProc = fc_defaultNoticeProcessor;

	fc_conn->status = CONNECTION_BAD;
	fc_conn->asyncStatus = PGASYNC_IDLE;
	fc_conn->pipelineStatus = PQ_PIPELINE_OFF;
	fc_conn->xactStatus = PQTRANS_IDLE;
	fc_conn->options_valid = false;
	fc_conn->nonblocking = false;
	fc_conn->client_encoding = PG_SQL_ASCII;
	fc_conn->std_strings = false;	/* 除非服务器另有说明 */
	fc_conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
	fc_conn->in_hot_standby = PG_BOOL_UNKNOWN;
	fc_conn->verbosity = PQERRORS_DEFAULT;
	fc_conn->show_context = PQSHOW_CONTEXT_ERRORS;
	fc_conn->sock = PGINVALID_SOCKET;
	fc_conn->Pfdebug = NULL;

	/*
	 * 我们尝试每次发送至少8K的数据，这是Unix系统中管道
	 * 缓冲区的通常大小。这样，当我们发送大量数据时，
	 * 可以避免因部分缓冲区加载而产生额外的内核上下文交换。
	 * 输出缓冲区初始大小为16K，我们在累计到8K后
	 * 尝试输出它。
	 *
	 * 以最小化上下文交换为目标，当输入缓冲区少于8K可用时，
	 * 它将被放大，因此我们最初分配两倍的大小。
	 */
	fc_conn->inBufSize = 16 * 1024;
	fc_conn->inBuffer = (char *) malloc(fc_conn->inBufSize);
	fc_conn->outBufSize = 16 * 1024;
	fc_conn->outBuffer = (char *) malloc(fc_conn->outBufSize);
	fc_conn->rowBufLen = 32;
	fc_conn->rowBuf = (PGdataValue *) malloc(fc_conn->rowBufLen * sizeof(PGdataValue));
	initPQExpBuffer(&fc_conn->errorMessage);
	initPQExpBuffer(&fc_conn->workBuffer);

	if (fc_conn->inBuffer == NULL ||
		fc_conn->outBuffer == NULL ||
		fc_conn->rowBuf == NULL ||
		PQExpBufferBroken(&fc_conn->errorMessage) ||
		PQExpBufferBroken(&fc_conn->workBuffer))
	{
		/* 内存不足 :-( */
		fc_freePGconn(fc_conn);
		fc_conn = NULL;
	}

	return fc_conn;
}

/*
 * freePGconn
 *	 - 释放一个空闲（关闭的）PGconn数据结构
 *
 * 注意：这不应与closePGconn()的任何功能重叠。
 * 瞬态状态的清除/重置属于后者；我们在这里所做的是
 * 释放在PGconn结构生存期间应保存的数据。
 * 如果某个值应该在PQreset()期间被清除/释放，
 * 就在那儿进行，而不是在这里。
 */
static void fc_freePGconn(PGconn *fc_conn)
{
	int			fc_i;

	/* 让任何事件处理程序清理它们的状态数据 */
	for (fc_i = 0; fc_i < fc_conn->nEvents; fc_i++)
	{
		PGEventConnDestroy fc_evt;

		fc_evt.conn = fc_conn;
		(void) fc_conn->events[fc_i].proc(PGEVT_CONNDESTROY, &fc_evt,
									fc_conn->events[fc_i].passThrough);
		free(fc_conn->events[fc_i].name);
	}

	/* 清理pg_conn_host结构 */
	if (fc_conn->connhost != NULL)
	{
		for (fc_i = 0; fc_i < fc_conn->nconnhost; ++fc_i)
		{
			if (fc_conn->connhost[fc_i].host != NULL)
				free(fc_conn->connhost[fc_i].host);
			if (fc_conn->connhost[fc_i].hostaddr != NULL)
				free(fc_conn->connhost[fc_i].hostaddr);
			if (fc_conn->connhost[fc_i].port != NULL)
				free(fc_conn->connhost[fc_i].port);
			if (fc_conn->connhost[fc_i].password != NULL)
			{
				explicit_bzero(fc_conn->connhost[fc_i].password, strlen(fc_conn->connhost[fc_i].password));
				free(fc_conn->connhost[fc_i].password);
			}
		}
		free(fc_conn->connhost);
	}

	if (fc_conn->client_encoding_initial)
		free(fc_conn->client_encoding_initial);
	if (fc_conn->events)
		free(fc_conn->events);
	if (fc_conn->pghost)
		free(fc_conn->pghost);
	if (fc_conn->pghostaddr)
		free(fc_conn->pghostaddr);
	if (fc_conn->pgport)
		free(fc_conn->pgport);
	if (fc_conn->connect_timeout)
		free(fc_conn->connect_timeout);
	if (fc_conn->pgtcp_user_timeout)
		free(fc_conn->pgtcp_user_timeout);
	if (fc_conn->pgoptions)
		free(fc_conn->pgoptions);
	if (fc_conn->appname)
		free(fc_conn->appname);
	if (fc_conn->fbappname)
		free(fc_conn->fbappname);
	if (fc_conn->dbName)
		free(fc_conn->dbName);
	if (fc_conn->replication)
		free(fc_conn->replication);
	if (fc_conn->pguser)
		free(fc_conn->pguser);
	if (fc_conn->pgpass)
	{
		explicit_bzero(fc_conn->pgpass, strlen(fc_conn->pgpass));
		free(fc_conn->pgpass);
	}
	if (fc_conn->pgpassfile)
		free(fc_conn->pgpassfile);
	if (fc_conn->channel_binding)
		free(fc_conn->channel_binding);
	if (fc_conn->keepalives)
		free(fc_conn->keepalives);
	if (fc_conn->keepalives_idle)
		free(fc_conn->keepalives_idle);
	if (fc_conn->keepalives_interval)
		free(fc_conn->keepalives_interval);
	if (fc_conn->keepalives_count)
		free(fc_conn->keepalives_count);
	if (fc_conn->sslmode)
		free(fc_conn->sslmode);
	if (fc_conn->sslcert)
		free(fc_conn->sslcert);
	if (fc_conn->sslkey)
		free(fc_conn->sslkey);
#ifdef FDDGM
if (fc_conn->sslenccert)
	free(fc_conn->sslenckey);
if (fc_conn->sslkey)
	free(fc_conn->sslenckey);

#endif
	if (fc_conn->sslpassword)
	{
		explicit_bzero(fc_conn->sslpassword, strlen(fc_conn->sslpassword));
		free(fc_conn->sslpassword);
	}
	if (fc_conn->sslrootcert)
		free(fc_conn->sslrootcert);
	if (fc_conn->sslcrl)
		free(fc_conn->sslcrl);
	if (fc_conn->sslcrldir)
		free(fc_conn->sslcrldir);
	if (fc_conn->sslcompression)
		free(fc_conn->sslcompression);
	if (fc_conn->sslsni)
		free(fc_conn->sslsni);
	if (fc_conn->requirepeer)
		free(fc_conn->requirepeer);
	if (fc_conn->ssl_min_protocol_version)
		free(fc_conn->ssl_min_protocol_version);
	if (fc_conn->ssl_max_protocol_version)
		free(fc_conn->ssl_max_protocol_version);
	if (fc_conn->gssencmode)
		free(fc_conn->gssencmode);
	if (fc_conn->krbsrvname)
		free(fc_conn->krbsrvname);
	if (fc_conn->gsslib)
		free(fc_conn->gsslib);
	if (fc_conn->connip)
		free(fc_conn->connip);
	/* 请注意conn->Pfdebug不是我们的，不能关闭或释放 */
	if (fc_conn->write_err_msg)
		free(fc_conn->write_err_msg);
	if (fc_conn->inBuffer)
		free(fc_conn->inBuffer);
	if (fc_conn->outBuffer)
		free(fc_conn->outBuffer);
	if (fc_conn->rowBuf)
		free(fc_conn->rowBuf);
	if (fc_conn->target_session_attrs)
		free(fc_conn->target_session_attrs);
	termPQExpBuffer(&fc_conn->errorMessage);
	termPQExpBuffer(&fc_conn->workBuffer);

	free(fc_conn);
}

/*
 * release_conn_addrinfo
 *	 - 释放PGconn中的任何addrinfo列表。
 */
static void fc_release_conn_addrinfo(PGconn *fc_conn)
{
	if (fc_conn->addrlist)
	{
		pg_freeaddrinfo_all(fc_conn->addrlist_family, fc_conn->addrlist);
		fc_conn->addrlist = NULL;
		fc_conn->addr_cur = NULL;	/* 出于安全考虑 */
	}
}

/*
 * sendTerminateConn
 *	 - 向后端发送终止消息。
 */
static void fc_sendTerminateConn(PGconn *fc_conn)
{
	/*
	 * 注意，该协议不允许我们在启动阶段发送终止消息。
	 */
	if (fc_conn->sock != PGINVALID_SOCKET && fc_conn->status == CONNECTION_OK)
	{
		/*
		 * 尝试向后端发送“关闭连接”消息。忽略任何
		 * 错误。
		 */
		pqPutMsgStart('X', fc_conn);
		pqPutMsgEnd(fc_conn);
		(void) pqFlush(fc_conn);
	}
}

/*
 * closePGconn
 *	 - 正确关闭与后端的连接
 *
 * 这应该重置或释放所有瞬态状态，但不包括连接
 * 参数。退出时，PGconn应该处于可以使用相同
 * 参数（见PQreset()）启动新连接的状态。
 */
static void fc_closePGconn(PGconn *fc_conn)
{
	/*
	 * 如果可能，发送终止消息以礼貌地关闭连接。
	 */
	fc_sendTerminateConn(fc_conn);

	/*
	 * 必须重置阻塞状态，以便可能的重新连接可以正常工作。
	 *
	 * 不要调用PQsetnonblocking()，因为如果无法
	 * 刷新连接，它将失败。
	 */
	fc_conn->nonblocking = false;

	/*
	 * 关闭连接，重置所有瞬态状态，刷新I/O缓冲区。
	 * 注意，这包括清除连接的错误状态；我们不再
	 * 对与旧连接相关的任何故障感兴趣，我们
	 * 希望为任何新的连接尝试提供一个干净的状态。
	 */
	pqDropConnection(fc_conn, true);
	fc_conn->status = CONNECTION_BAD;	/* 嗯，其实并不算_坏_ - 只是缺失 */
	fc_conn->asyncStatus = PGASYNC_IDLE;
	fc_conn->xactStatus = PQTRANS_IDLE;
	fc_conn->pipelineStatus = PQ_PIPELINE_OFF;
	pqClearAsyncResult(fc_conn);	/* 释放结果 */
	pqClearConnErrorState(fc_conn);
	fc_release_conn_addrinfo(fc_conn);

	/* 也重置从服务器获得的所有状态 */
	fc_pqDropServerData(fc_conn);
}

/*
 * PQfinish: 正确关闭与后端的连接。还会释放
 * PGconn 数据结构，因此在此之后不应重用。
 */
void PQfinish(PGconn *fc_conn)
{
	if (fc_conn)
	{
		fc_closePGconn(fc_conn);
		fc_freePGconn(fc_conn);
	}
}

/*
 * PQreset: 通过关闭
 * 现有连接并创建一个新连接来重置与后端的连接。
 */
void PQreset(PGconn *fc_conn)
{
	if (fc_conn)
	{
		fc_closePGconn(fc_conn);

		if (fc_connectDBStart(fc_conn) && fc_connectDBComplete(fc_conn))
		{
			/*
			 * 通知事件处理程序重置成功。
			 */
			int			fc_i;

			for (fc_i = 0; fc_i < fc_conn->nEvents; fc_i++)
			{
				PGEventConnReset fc_evt;

				fc_evt.conn = fc_conn;
				(void) fc_conn->events[fc_i].proc(PGEVT_CONNRESET, &fc_evt,
											fc_conn->events[fc_i].passThrough);
			}
		}
	}
}


/*
 * PQresetStart:
 * 重置与后端的连接
 * 关闭现有连接并建立新连接
 * 成功时返回 1，失败时返回 0。
 */
int PQresetStart(PGconn *fc_conn)
{
	if (fc_conn)
	{
		fc_closePGconn(fc_conn);

		return fc_connectDBStart(fc_conn);
	}

	return 0;
}


/*
 * PQresetPoll:
 * 重置与后端的连接
 * 关闭现有连接并建立新连接
 */
PostgresPollingStatusType PQresetPoll(PGconn *fc_conn)
{
	if (fc_conn)
	{
		PostgresPollingStatusType fc_status = PQconnectPoll(fc_conn);

		if (fc_status == PGRES_POLLING_OK)
		{
			/*
			 * 通知事件处理程序重置成功。
			 */
			int			fc_i;

			for (fc_i = 0; fc_i < fc_conn->nEvents; fc_i++)
			{
				PGEventConnReset fc_evt;

				fc_evt.conn = fc_conn;
				(void) fc_conn->events[fc_i].proc(PGEVT_CONNRESET, &fc_evt,
											fc_conn->events[fc_i].passThrough);
			}
		}

		return fc_status;
	}

	return PGRES_POLLING_FAILED;
}

/*
 * PQgetCancel: 获取与连接对应的 PGcancel 结构。
 *
 * 需要复制以便能够从不同的线程取消正在运行的查询。
 * 如果使用相同的结构，所有结构成员都必须单独锁定
 * （如果整个结构被锁定，将无法取消同步查询，因为该结构
 * 必须在查询持续期间保持锁定）。
 */
PGcancel *
PQgetCancel(PGconn *fc_conn)
{
	PGcancel   *fc_cancel;

	if (!fc_conn)
		return NULL;

	if (fc_conn->sock == PGINVALID_SOCKET)
		return NULL;

	fc_cancel = malloc(sizeof(PGcancel));
	if (fc_cancel == NULL)
		return NULL;

	memcpy(&fc_cancel->raddr, &fc_conn->raddr, sizeof(SockAddr));
	fc_cancel->be_pid = fc_conn->be_pid;
	fc_cancel->be_key = fc_conn->be_key;
	/* 我们使用 -1 来表示未设置的连接选项 */
	fc_cancel->pgtcp_user_timeout = -1;
	fc_cancel->keepalives = -1;
	fc_cancel->keepalives_idle = -1;
	fc_cancel->keepalives_interval = -1;
	fc_cancel->keepalives_count = -1;
	if (fc_conn->pgtcp_user_timeout != NULL)
	{
		if (!fc_parse_int_param(fc_conn->pgtcp_user_timeout,
							 &fc_cancel->pgtcp_user_timeout,
							 fc_conn, "tcp_user_timeout"))
			goto fail;
	}
	if (fc_conn->keepalives != NULL)
	{
		if (!fc_parse_int_param(fc_conn->keepalives,
							 &fc_cancel->keepalives,
							 fc_conn, "keepalives"))
			goto fail;
	}
	if (fc_conn->keepalives_idle != NULL)
	{
		if (!fc_parse_int_param(fc_conn->keepalives_idle,
							 &fc_cancel->keepalives_idle,
							 fc_conn, "keepalives_idle"))
			goto fail;
	}
	if (fc_conn->keepalives_interval != NULL)
	{
		if (!fc_parse_int_param(fc_conn->keepalives_interval,
							 &fc_cancel->keepalives_interval,
							 fc_conn, "keepalives_interval"))
			goto fail;
	}
	if (fc_conn->keepalives_count != NULL)
	{
		if (!fc_parse_int_param(fc_conn->keepalives_count,
							 &fc_cancel->keepalives_count,
							 fc_conn, "keepalives_count"))
			goto fail;
	}

	return fc_cancel;

fail:
	free(fc_cancel);
	return NULL;
}

/* PQfreeCancel: 释放取消结构 */
void PQfreeCancel(PGcancel *fc_cancel)
{
	if (fc_cancel)
		free(fc_cancel);
}


/*
 * 在 TCP 套接字上设置一个整数套接字选项，如果提供的值
 * 不是负数。 如果因为某种原因 setsockopt 失败，返回 false。
 *
 * 注意：这需要是信号安全的，因为它被 PQcancel 使用。
 */
#if defined(TCP_USER_TIMEOUT) || !defined(WIN32)
static bool fc_optional_setsockopt(int fc_fd, int fc_protoid, int fc_optid, int fc_value)
{
	if (fc_value < 0)
		return true;
	if (setsockopt(fc_fd, fc_protoid, fc_optid, (char *) &fc_value, sizeof(fc_value)) < 0)
		return false;
	return true;
}
#endif


/*
 * PQcancel: 请求取消查询
 *
 * 如果取消请求成功调度，返回值为 true，反之为 false 
 * （在这种情况下，有可用的错误消息）。
 * 注意：成功调度并不能保证在后端会有任何效果。
 * 应用程序必须像往常一样读取操作结果。
 *
 * 失败时，错误消息存储在 *errbuf 中，*errbuf 的大小必须为 
 * errbufsize（推荐大小为 256 字节）。成功返回时*errbuf 不会改变。
 *
 * 注意：我们希望这个例程可以安全地从信号处理程序调用
 * （例如，应用程序可能希望在 SIGINT 处理程序中调用它）。
 * 这意味着我们不能使用任何可能是非可重入的 C 库例程。
 * malloc/free 通常是非可重入的，任何可能调用它们的内容
 * 都同样危险。出于这个原因，我们在这里避免使用 sprintf。
 * 使用 strcpy/strcat 构建错误消息虽然繁琐，但应该是相当安全的。
 * 我们还保存/恢复 errno，以防信号处理程序支持不工作。
 */
int PQcancel(PGcancel *fc_cancel, char *fc_errbuf, int fc_errbufsize)
{
	int			fc_save_errno = SOCK_ERRNO;
	pgsocket	fc_tmpsock = PGINVALID_SOCKET;
	int			fc_maxlen;
	struct
	{
		uint32		packetlen;
		CancelRequestPacket cp;
	}			fc_crp;

	if (!fc_cancel)
	{
		strlcpy(fc_errbuf, "PQcancel() -- no cancel object supplied", fc_errbufsize);
		/* strlcpy 可能不会改变 errno，但保持警惕 */
		SOCK_ERRNO_SET(fc_save_errno);
		return false;
	}

	/*
	 * 我们需要与主进程打开一个临时连接。仅通过
	 * 内核调用来完成此操作。
	 */
	if ((fc_tmpsock = socket(fc_cancel->raddr.addr.ss_family, SOCK_STREAM, 0)) == PGINVALID_SOCKET)
	{
		strlcpy(fc_errbuf, "PQcancel() -- socket() failed: ", fc_errbufsize);
		goto cancel_errReturn;
	}

	/*
	 * 由于这个连接只会用于发送一个数据包，我们不需要NODELAY。我们也不将套接字设置为非阻塞模式，因为PQcancel的API定义要求取消必须以阻塞方式发送。
	 *
	 * 我们确实设置与保活和其他TCP超时相关的套接字选项。这确保了当提供了合理的保活和超时设置时，此功能不会无限期阻塞。
	 */
	if (fc_cancel->raddr.addr.ss_family != AF_UNIX &&
		fc_cancel->keepalives != 0)
	{
#ifndef WIN32
		if (!fc_optional_setsockopt(fc_tmpsock, SOL_SOCKET, SO_KEEPALIVE, 1))
		{
			strlcpy(fc_errbuf, "PQcancel() -- setsockopt(SO_KEEPALIVE) failed: ", fc_errbufsize);
			goto cancel_errReturn;
		}

#ifdef PG_TCP_KEEPALIVE_IDLE
		if (!fc_optional_setsockopt(fc_tmpsock, IPPROTO_TCP, PG_TCP_KEEPALIVE_IDLE,
								 fc_cancel->keepalives_idle))
		{
			strlcpy(fc_errbuf, "PQcancel() -- setsockopt(" PG_TCP_KEEPALIVE_IDLE_STR ") failed: ", fc_errbufsize);
			goto cancel_errReturn;
		}
#endif

#ifdef TCP_KEEPINTVL
		if (!fc_optional_setsockopt(fc_tmpsock, IPPROTO_TCP, TCP_KEEPINTVL,
								 fc_cancel->keepalives_interval))
		{
			strlcpy(fc_errbuf, "PQcancel() -- setsockopt(TCP_KEEPINTVL) failed: ", fc_errbufsize);
			goto cancel_errReturn;
		}
#endif

#ifdef TCP_KEEPCNT
		if (!fc_optional_setsockopt(fc_tmpsock, IPPROTO_TCP, TCP_KEEPCNT,
								 fc_cancel->keepalives_count))
		{
			strlcpy(fc_errbuf, "PQcancel() -- setsockopt(TCP_KEEPCNT) failed: ", fc_errbufsize);
			goto cancel_errReturn;
		}
#endif

#else							/* WIN32 */

#ifdef SIO_KEEPALIVE_VALS
		if (!setKeepalivesWin32(fc_tmpsock,
								fc_cancel->keepalives_idle,
								fc_cancel->keepalives_interval))
		{
			strlcpy(fc_errbuf, "PQcancel() -- WSAIoctl(SIO_KEEPALIVE_VALS) failed: ", fc_errbufsize);
			goto cancel_errReturn;
		}
#endif							/* SIO_KEEPALIVE_VALS */
#endif							/* WIN32 */

		/* TCP_USER_TIMEOUT在Unix和Windows上表现相同 */
#ifdef TCP_USER_TIMEOUT
		if (!fc_optional_setsockopt(fc_tmpsock, IPPROTO_TCP, TCP_USER_TIMEOUT,
								 fc_cancel->pgtcp_user_timeout))
		{
			strlcpy(fc_errbuf, "PQcancel() -- setsockopt(TCP_USER_TIMEOUT) failed: ", fc_errbufsize);
			goto cancel_errReturn;
		}
#endif
	}

retry3:
	if (connect(fc_tmpsock, (struct sockaddr *) &fc_cancel->raddr.addr,
				fc_cancel->raddr.salen) < 0)
	{
		if (SOCK_ERRNO == EINTR)
			/* 中断系统调用 - 我们会再试一次 */
			goto retry3;
		strlcpy(fc_errbuf, "PQcancel() -- connect() failed: ", fc_errbufsize);
		goto cancel_errReturn;
	}

	/* 创建并发送取消请求数据包。 */

	fc_crp.packetlen = pg_hton32((uint32) sizeof(fc_crp));
	fc_crp.cp.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE);
	fc_crp.cp.backendPID = pg_hton32(fc_cancel->be_pid);
	fc_crp.cp.cancelAuthCode = pg_hton32(fc_cancel->be_key);

retry4:
	if (send(fc_tmpsock, (char *) &fc_crp, sizeof(fc_crp), 0) != (int) sizeof(fc_crp))
	{
		if (SOCK_ERRNO == EINTR)
			/* 中断系统调用 - 我们会再试一次 */
			goto retry4;
		strlcpy(fc_errbuf, "PQcancel() -- send() failed: ", fc_errbufsize);
		goto cancel_errReturn;
	}

	/*
	 * 等待主进程关闭连接，这表示它已经处理了请求。没有这个延迟，我们可能会发出另一个命令，却发现我们的取消命令消除了那个命令，而不是我们认为要取消的命令。请注意，我们实际上并不期待这次读取会获得任何数据，我们只是等待EOF信号。
	 */
retry5:
	if (recv(fc_tmpsock, (char *) &fc_crp, 1, 0) < 0)
	{
		if (SOCK_ERRNO == EINTR)
			/* 中断系统调用 - 我们会再试一次 */
			goto retry5;
		/* 我们忽略其他错误条件 */
	}

	/* 完成 */
	closesocket(fc_tmpsock);
	SOCK_ERRNO_SET(fc_save_errno);
	return true;

cancel_errReturn:

	/*
	 * 确保我们不会溢出错误缓冲区。为末尾的\n和终止的零留出空间。
	 */
	fc_maxlen = fc_errbufsize - strlen(fc_errbuf) - 2;
	if (fc_maxlen >= 0)
	{
		/*
		 * 我们无法在这里调用strerror，因为它不是信号安全的。只能选择打印errno的十进制值。即便如此，也必须以艰难的方式完成。
		 */
		int			fc_val = SOCK_ERRNO;
		char		fc_buf[32];
		char	   *fc_bufp;

		fc_bufp = fc_buf + sizeof(fc_buf) - 1;
		*fc_bufp = '\0';
		do
		{
			*(--fc_bufp) = (fc_val % 10) + '0';
			fc_val /= 10;
		} while (fc_val > 0);
		fc_bufp -= 6;
		memcpy(fc_bufp, "error ", 6);
		strncat(fc_errbuf, fc_bufp, fc_maxlen);
		strcat(fc_errbuf, "\n");
	}
	if (fc_tmpsock != PGINVALID_SOCKET)
		closesocket(fc_tmpsock);
	SOCK_ERRNO_SET(fc_save_errno);
	return false;
}


/*
 * PQrequestCancel：旧的、不安全的请求查询取消的线程函数
 *
 * 如果能够发送取消请求则返回true，否则返回false。
 *
 * 在失败时，错误信息保存在conn->errorMessage中；这意味着当连接对象可能有其他活动操作时不能使用。
 *
 * 注意：错误信息将在当前错误信息缓冲区的大小处被截断，因为我们不敢尝试扩展conn->errorMessage！
 */
int PQrequestCancel(PGconn *fc_conn)
{
	int			fc_r;
	PGcancel   *fc_cancel;

	/* 检查我们是否有一个开放的连接 */
	if (!fc_conn)
		return false;

	if (fc_conn->sock == PGINVALID_SOCKET)
	{
		strlcpy(fc_conn->errorMessage.data,
				"PQrequestCancel() -- connection is not open\n",
				fc_conn->errorMessage.maxlen);
		fc_conn->errorMessage.len = strlen(fc_conn->errorMessage.data);
		fc_conn->errorReported = 0;

		return false;
	}

	fc_cancel = PQgetCancel(fc_conn);
	if (fc_cancel)
	{
		fc_r = PQcancel(fc_cancel, fc_conn->errorMessage.data,
					 fc_conn->errorMessage.maxlen);
		PQfreeCancel(fc_cancel);
	}
	else
	{
		strlcpy(fc_conn->errorMessage.data, "out of memory",
				fc_conn->errorMessage.maxlen);
		fc_r = false;
	}

	if (!fc_r)
	{
		fc_conn->errorMessage.len = strlen(fc_conn->errorMessage.data);
		fc_conn->errorReported = 0;
	}

	return fc_r;
}


/*
 * pqPacketSend() -- 发送消息到服务器的便利例程。
 *
 * pack_type：单字节消息类型代码。（对于没有消息类型代码的启动数据包传递零。）
 *
 * buf，buf_len：消息的内容。给定的长度仅包括buf中的内容；消息类型和消息长度字段在这里添加。
 *
 * 返回：如果写入失败则STATUS_ERROR，否则STATUS_OK。
 * 副作用：可能会阻塞。
 */
int pqPacketSend(PGconn *fc_conn, char fc_pack_type,
			 const void *fc_buf, size_t fc_buf_len)
{
	/* 开始消息。 */
	if (pqPutMsgStart(fc_pack_type, fc_conn))
		return STATUS_ERROR;

	/* 发送消息主体。 */
	if (pqPutnchar(fc_buf, fc_buf_len, fc_conn))
		return STATUS_ERROR;

	/* 完成消息。 */
	if (pqPutMsgEnd(fc_conn))
		return STATUS_ERROR;

	/* 刷新以确保后端接收到。 */
	if (pqFlush(fc_conn))
		return STATUS_ERROR;

	return STATUS_OK;
}

#ifdef USE_LDAP

#define LDAP_URL	"ldap://"
#define LDAP_DEF_PORT	389
#define PGLDAP_TIMEOUT 2

#define ld_is_sp_tab(x) ((x) == ' ' || (x) == '\t')
#define ld_is_nl_cr(x) ((x) == '\r' || (x) == '\n')


/*
 *		ldapServiceLookup
 *
 * 搜索作为第一个参数传递的 LDAP URL，将结果视为
 * 连接选项的字符串，这些选项将被解析并添加到作为第二个参数传递的选项数组中。
 *
 * LDAP URL 必须符合 RFC 1959 而没有转义序列。
 *	ldap://host:port/dn?attributes?scope?filter?extensions
 *
 * 返回
 *	0 如果查找成功，
 *	1 如果能与 LDAP 服务器建立连接，但
 *	  搜索不成功，
 *	2 如果无法建立连接，以及
 *	3 如果发生严重错误。
 *
 * 对于返回代码 1 和 3，错误消息将附加到 *errorMessage。
 */
static int fc_ldapServiceLookup(const char *fc_purl, PQconninfoOption *fc_options,
				  PQExpBuffer fc_errorMessage)
{
	int			fc_port = LDAP_DEF_PORT,
				fc_scope,
				fc_rc,
				fc_size,
				fc_state,
				fc_oldstate,
				fc_i;
#ifndef WIN32
	int			fc_msgid;
#endif
	bool		fc_found_keyword;
	char	   *fc_url,
			   *fc_hostname,
			   *fc_portstr,
			   *fc_endptr,
			   *fc_dn,
			   *fc_scopestr,
			   *fc_filter,
			   *fc_result,
			   *fc_p,
			   *fc_p1 = NULL,
			   *fc_optname = NULL,
			   *fc_optval = NULL;
	char	   *fc_attrs[2] = {NULL, NULL};
	LDAP	   *fc_ld = NULL;
	LDAPMessage *fc_res,
			   *fc_entry;
	struct berval **fc_values;
	LDAP_TIMEVAL fc_time = {PGLDAP_TIMEOUT, 0};

	if ((fc_url = strdup(fc_purl)) == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage, libpq_gettext("out of memory\n"));
		return 3;
	}

	/*
	 * 解析 URL 组件，检查其正确性。基本上，URL 在组件边界上放置了 '\0'
	 * 并且变量指向每个组件。
	 */

	if (pg_strncasecmp(fc_url, LDAP_URL, strlen(LDAP_URL)) != 0)
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid LDAP URL \"%s\": scheme must be ldap://\n"), fc_purl);
		free(fc_url);
		return 3;
	}

	/* 主机名 */
	fc_hostname = fc_url + strlen(LDAP_URL);
	if (*fc_hostname == '/')		/* 没有主机名？ */
		fc_hostname = DefaultHost; /* 默认值 */

	/* dn，"可分辨名称" */
	fc_p = strchr(fc_url + strlen(LDAP_URL), '/');
	if (fc_p == NULL || *(fc_p + 1) == '\0' || *(fc_p + 1) == '?')
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid LDAP URL \"%s\": missing distinguished name\n"),
						  fc_purl);
		free(fc_url);
		return 3;
	}
	*fc_p = '\0';					/* 终止主机名 */
	fc_dn = fc_p + 1;

	/* 属性 */
	if ((fc_p = strchr(fc_dn, '?')) == NULL || *(fc_p + 1) == '\0' || *(fc_p + 1) == '?')
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid LDAP URL \"%s\": must have exactly one attribute\n"),
						  fc_purl);
		free(fc_url);
		return 3;
	}
	*fc_p = '\0';
	fc_attrs[0] = fc_p + 1;

	/* 范围 */
	if ((fc_p = strchr(fc_attrs[0], '?')) == NULL || *(fc_p + 1) == '\0' || *(fc_p + 1) == '?')
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid LDAP URL \"%s\": must have search scope (base/one/sub)\n"),
						  fc_purl);
		free(fc_url);
		return 3;
	}
	*fc_p = '\0';
	fc_scopestr = fc_p + 1;

	/* 过滤器 */
	if ((fc_p = strchr(fc_scopestr, '?')) == NULL || *(fc_p + 1) == '\0' || *(fc_p + 1) == '?')
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid LDAP URL \"%s\": no filter\n"),
						  fc_purl);
		free(fc_url);
		return 3;
	}
	*fc_p = '\0';
	fc_filter = fc_p + 1;
	if ((fc_p = strchr(fc_filter, '?')) != NULL)
		*fc_p = '\0';

	/* 端口号？ */
	if ((fc_p1 = strchr(fc_hostname, ':')) != NULL)
	{
		long		fc_lport;

		*fc_p1 = '\0';
		fc_portstr = fc_p1 + 1;
		errno = 0;
		fc_lport = strtol(fc_portstr, &fc_endptr, 10);
		if (*fc_portstr == '\0' || *fc_endptr != '\0' || errno || fc_lport < 0 || fc_lport > 65535)
		{
			appendPQExpBuffer(fc_errorMessage,
							  libpq_gettext("invalid LDAP URL \"%s\": invalid port number\n"),
							  fc_purl);
			free(fc_url);
			return 3;
		}
		fc_port = (int) fc_lport;
	}

	/* 只允许一个属性 */
	if (strchr(fc_attrs[0], ',') != NULL)
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid LDAP URL \"%s\": must have exactly one attribute\n"),
						  fc_purl);
		free(fc_url);
		return 3;
	}

	/* 设置范围 */
	if (pg_strcasecmp(fc_scopestr, "base") == 0)
		fc_scope = LDAP_SCOPE_BASE;
	else if (pg_strcasecmp(fc_scopestr, "one") == 0)
		fc_scope = LDAP_SCOPE_ONELEVEL;
	else if (pg_strcasecmp(fc_scopestr, "sub") == 0)
		fc_scope = LDAP_SCOPE_SUBTREE;
	else
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid LDAP URL \"%s\": must have search scope (base/one/sub)\n"),
						  fc_purl);
		free(fc_url);
		return 3;
	}

	/* 初始化 LDAP 结构 */
	if ((fc_ld = ldap_init(fc_hostname, fc_port)) == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("could not create LDAP structure\n"));
		free(fc_url);
		return 3;
	}

	/*
	 * 执行显式匿名绑定。
	 *
	 * LDAP 不要求显式执行匿名绑定，
	 * 但是我们想要区分 LDAP 绑定在 PGLDAP_TIMEOUT 秒内未成功的情况
	 * （返回 2 以继续解析服务控制文件）和
	 * 查询 LDAP 服务器失败的情况
	 * （返回 1 以结束解析）。
	 *
	 * 不幸的是，没有办法设置同时对 Windows 和 OpenLDAP 有效的超时。
	 */
#ifdef WIN32
	/* 非标准的 ldap_connect 函数执行匿名绑定 */
	if (ldap_connect(ld, &time) != LDAP_SUCCESS)
	{
		/* ldap_connect 中的错误或超时 */
		free(url);
		ldap_unbind(ld);
		return 2;
	}
#else							/* !WIN32 */
	/* 在 OpenLDAP 中，使用 LDAP_OPT_NETWORK_TIMEOUT 选项 */
	if (ldap_set_option(fc_ld, LDAP_OPT_NETWORK_TIMEOUT, &fc_time) != LDAP_SUCCESS)
	{
		free(fc_url);
		ldap_unbind(fc_ld);
		return 3;
	}

	/* 匿名绑定 */
	if ((fc_msgid = ldap_simple_bind(fc_ld, NULL, NULL)) == -1)
	{
		/* 错误或网络超时 */
		free(fc_url);
		ldap_unbind(fc_ld);
		return 2;
	}

	/* 等待一段时间以使连接成功 */
	fc_res = NULL;
	if ((fc_rc = ldap_result(fc_ld, fc_msgid, LDAP_MSG_ALL, &fc_time, &fc_res)) == -1 ||
		fc_res == NULL)
	{
		/* 错误或超时 */
		if (fc_res != NULL)
			ldap_msgfree(fc_res);
		free(fc_url);
		ldap_unbind(fc_ld);
		return 2;
	}
	ldap_msgfree(fc_res);

	/* 重置超时 */
	fc_time.tv_sec = -1;
	if (ldap_set_option(fc_ld, LDAP_OPT_NETWORK_TIMEOUT, &fc_time) != LDAP_SUCCESS)
	{
		free(fc_url);
		ldap_unbind(fc_ld);
		return 3;
	}
#endif							/* WIN32 */

	/* 搜索 */
	fc_res = NULL;
	if ((fc_rc = ldap_search_st(fc_ld, fc_dn, fc_scope, fc_filter, fc_attrs, 0, &fc_time, &fc_res))
		!= LDAP_SUCCESS)
	{
		if (fc_res != NULL)
			ldap_msgfree(fc_res);
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("lookup on LDAP server failed: %s\n"),
						  ldap_err2string(fc_rc));
		ldap_unbind(fc_ld);
		free(fc_url);
		return 1;
	}

	/* 如果没有正好一个结果则投诉 */
	if ((fc_rc = ldap_count_entries(fc_ld, fc_res)) != 1)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 fc_rc ? libpq_gettext("more than one entry found on LDAP lookup\n")
							 : libpq_gettext("no entry found on LDAP lookup\n"));
		ldap_msgfree(fc_res);
		ldap_unbind(fc_ld);
		free(fc_url);
		return 1;
	}

	/* 获取条目 */
	if ((fc_entry = ldap_first_entry(fc_ld, fc_res)) == NULL)
	{
		/* 永远不应该发生 */
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("no entry found on LDAP lookup\n"));
		ldap_msgfree(fc_res);
		ldap_unbind(fc_ld);
		free(fc_url);
		return 1;
	}

	/* 获取值 */
	if ((fc_values = ldap_get_values_len(fc_ld, fc_entry, fc_attrs[0])) == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("attribute has no values on LDAP lookup\n"));
		ldap_msgfree(fc_res);
		ldap_unbind(fc_ld);
		free(fc_url);
		return 1;
	}

	ldap_msgfree(fc_res);
	free(fc_url);

	if (fc_values[0] == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("attribute has no values on LDAP lookup\n"));
		ldap_value_free_len(fc_values);
		ldap_unbind(fc_ld);
		return 1;
	}

	/* 将值连接成带换行符的单个字符串 */
	fc_size = 1;					/* 用于尾随的 null */
	for (fc_i = 0; fc_values[fc_i] != NULL; fc_i++)
		fc_size += fc_values[fc_i]->bv_len + 1;
	if ((fc_result = malloc(fc_size)) == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("out of memory\n"));
		ldap_value_free_len(fc_values);
		ldap_unbind(fc_ld);
		return 3;
	}
	fc_p = fc_result;
	for (fc_i = 0; fc_values[fc_i] != NULL; fc_i++)
	{
		memcpy(fc_p, fc_values[fc_i]->bv_val, fc_values[fc_i]->bv_len);
		fc_p += fc_values[fc_i]->bv_len;
		*(fc_p++) = '\n';
	}
	*fc_p = '\0';

	ldap_value_free_len(fc_values);
	ldap_unbind(fc_ld);

	/* 解析结果字符串 */
	fc_oldstate = fc_state = 0;
	for (fc_p = fc_result; *fc_p != '\0'; ++fc_p)
	{
		switch (fc_state)
		{
			case 0:				/* 在条目之间 */
				if (!ld_is_sp_tab(*fc_p) && !ld_is_nl_cr(*fc_p))
				{
					fc_optname = fc_p;
					fc_state = 1;
				}
				break;
			case 1:				/* 在选项名称中 */
				if (ld_is_sp_tab(*fc_p))
				{
					*fc_p = '\0';
					fc_state = 2;
				}
				else if (ld_is_nl_cr(*fc_p))
				{
					appendPQExpBuffer(fc_errorMessage,
									  libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
									  fc_optname);
					free(fc_result);
					return 3;
				}
				else if (*fc_p == '=')
				{
					*fc_p = '\0';
					fc_state = 3;
				}
				break;
			case 2:				/* 在选项名称之后 */
				if (*fc_p == '=')
				{
					fc_state = 3;
				}
				else if (!ld_is_sp_tab(*fc_p))
				{
					appendPQExpBuffer(fc_errorMessage,
									  libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
									  fc_optname);
					free(fc_result);
					return 3;
				}
				break;
			case 3:				/* 在选项值之前 */
				if (*fc_p == '\'')
				{
					fc_optval = fc_p + 1;
					fc_p1 = fc_p + 1;
					fc_state = 5;
				}
				else if (ld_is_nl_cr(*fc_p))
				{
					fc_optval = fc_optname + strlen(fc_optname); /* 空 */
					fc_state = 0;
				}
				else if (!ld_is_sp_tab(*fc_p))
				{
					fc_optval = fc_p;
					fc_state = 4;
				}
				break;
			case 4:				/* 在未引用的选项值中 */
				if (ld_is_sp_tab(*fc_p) || ld_is_nl_cr(*fc_p))
				{
					*fc_p = '\0';
					fc_state = 0;
				}
				break;
			case 5:				/* 在引用的选项值中 */
				if (*fc_p == '\'')
				{
					*fc_p1 = '\0';
					fc_state = 0;
				}
				else if (*fc_p == '\\')
					fc_state = 6;
				else
					*(fc_p1++) = *fc_p;
				break;
			case 6:				/* 在转义后的引用选项值中 */
				*(fc_p1++) = *fc_p;
				fc_state = 5;
				break;
		}

		if (fc_state == 0 && fc_oldstate != 0)
		{
			fc_found_keyword = false;
			for (fc_i = 0; fc_options[fc_i].keyword; fc_i++)
			{
				if (strcmp(fc_options[fc_i].keyword, fc_optname) == 0)
				{
					if (fc_options[fc_i].val == NULL)
					{
						fc_options[fc_i].val = strdup(fc_optval);
						if (!fc_options[fc_i].val)
						{
							appendPQExpBufferStr(fc_errorMessage,
												 libpq_gettext("out of memory\n"));
							free(fc_result);
							return 3;
						}
					}
					fc_found_keyword = true;
					break;
				}
			}
			if (!fc_found_keyword)
			{
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("invalid connection option \"%s\"\n"),
								  fc_optname);
				free(fc_result);
				return 1;
			}
			fc_optname = NULL;
			fc_optval = NULL;
		}
		fc_oldstate = fc_state;
	}

	free(fc_result);

	if (fc_state == 5 || fc_state == 6)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("unterminated quoted string in connection info string\n"));
		return 3;
	}

	return 0;
}

#endif							/* USE_LDAP */

/*
 * parseServiceInfo: 如果给定了服务名称，则查找并将连接选项吸收
 * 到*options中。
 *
 * 成功返回0，失败返回非零值。在失败的情况下，如果errorMessage
 * 不是null，也会将错误消息存储在那里。（注意：此函数及相关函数
 * 在errorMessage == NULL时不会崩溃的唯一原因是，
 * appendPQExpBuffer在传入null PQExpBuffer指针时不会执行任何操作。）
 */
static int fc_parseServiceInfo(PQconninfoOption *fc_options, PQExpBuffer fc_errorMessage)
{
	const char *fc_service = fc_conninfo_getval(fc_options, "service");
	char		fc_serviceFile[MAXPGPATH];
	char	   *fc_env;
	bool		fc_group_found = false;
	int			fc_status;
	struct stat fc_stat_buf;

	/*
	 * 我们必须在这里特殊处理环境变量PGSERVICE，因为
	 * 这应该在插入其他连接选项的环境默认值之前调用。
	 */
	if (fc_service == NULL)
		fc_service = getenv("PGSERVICE");

	/* 如果没有给出服务名称，则无需处理 */
	if (fc_service == NULL)
		return 0;

	/*
	 * 如果指定了PGSERVICEFILE，则尝试使用它，否则尝试 ~/.pg_service.conf
	 * （如果存在）。
	 */
	if ((fc_env = getenv("PGSERVICEFILE")) != NULL)
		strlcpy(fc_serviceFile, fc_env, sizeof(fc_serviceFile));
	else
	{
		char		fc_homedir[MAXPGPATH];

		if (!pqGetHomeDirectory(fc_homedir, sizeof(fc_homedir)))
			goto next_file;
		snprintf(fc_serviceFile, MAXPGPATH, "%s/%s", fc_homedir, ".pg_service.conf");
		if (stat(fc_serviceFile, &fc_stat_buf) != 0)
			goto next_file;
	}

	fc_status = fc_parseServiceFile(fc_serviceFile, fc_service, fc_options, fc_errorMessage, &fc_group_found);
	if (fc_group_found || fc_status != 0)
		return fc_status;

next_file:

	/*
	 * 这可以被任何应用程序使用，因此我们不能使用二进制
	 * 位置来查找我们的配置文件。
	 */
	snprintf(fc_serviceFile, MAXPGPATH, "%s/pg_service.conf",
			 getenv("PGSYSCONFDIR") ? getenv("PGSYSCONFDIR") : SYSCONFDIR);
	if (stat(fc_serviceFile, &fc_stat_buf) != 0)
		goto last_file;

	fc_status = fc_parseServiceFile(fc_serviceFile, fc_service, fc_options, fc_errorMessage, &fc_group_found);
	if (fc_status != 0)
		return fc_status;

last_file:
	if (!fc_group_found)
	{
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("definition of service \"%s\" not found\n"), fc_service);
		return 3;
	}

	return 0;
}

static int fc_parseServiceFile(const char *fc_serviceFile,
				 const char *fc_service,
				 PQconninfoOption *fc_options,
				 PQExpBuffer fc_errorMessage,
				 bool *fc_group_found)
{
	int			fc_result = 0,
				fc_linenr = 0,
				fc_i;
	FILE	   *fc_f;
	char	   *fc_line;
	char		fc_buf[1024];

	*fc_group_found = false;

	fc_f = fopen(fc_serviceFile, "r");
	if (fc_f == NULL)
	{
		appendPQExpBuffer(fc_errorMessage, libpq_gettext("service file \"%s\" not found\n"),
						  fc_serviceFile);
		return 1;
	}

	while ((fc_line = fgets(fc_buf, sizeof(fc_buf), fc_f)) != NULL)
	{
		int			fc_len;

		fc_linenr++;

		if (strlen(fc_line) >= sizeof(fc_buf) - 1)
		{
			appendPQExpBuffer(fc_errorMessage,
							  libpq_gettext("line %d too long in service file \"%s\"\n"),
							  fc_linenr,
							  fc_serviceFile);
			fc_result = 2;
			goto exit;
		}

		/* 忽略行尾的空白字符，尤其是换行符 */
		fc_len = strlen(fc_line);
		while (fc_len > 0 && isspace((unsigned char) fc_line[fc_len - 1]))
			fc_line[--fc_len] = '\0';

		/* 也要忽略前导空白 */
		while (*fc_line && isspace((unsigned char) fc_line[0]))
			fc_line++;

		/* 忽略注释和空行 */
		if (fc_line[0] == '\0' || fc_line[0] == '#')
			continue;

		/* 检查正确的组名 */
		if (fc_line[0] == '[')
		{
			if (*fc_group_found)
			{
				/* 到达所需组的末尾；返回成功 */
				goto exit;
			}

			if (strncmp(fc_line + 1, fc_service, strlen(fc_service)) == 0 &&
				fc_line[strlen(fc_service) + 1] == ']')
				*fc_group_found = true;
			else
				*fc_group_found = false;
		}
		else
		{
			if (*fc_group_found)
			{
				/*
				 * 最后，我们进入了正确的组，可以解析该行
				 */
				char	   *fc_key,
						   *fc_val;
				bool		fc_found_keyword;

#ifdef USE_LDAP
				if (strncmp(fc_line, "ldap", 4) == 0)
				{
					int			fc_rc = fc_ldapServiceLookup(fc_line, fc_options, fc_errorMessage);

					/* 如果rc = 2，继续读取以进行回退 */
					switch (fc_rc)
					{
						case 0:
							goto exit;
						case 1:
						case 3:
							fc_result = 3;
							goto exit;
						case 2:
							continue;
					}
				}
#endif

				fc_key = fc_line;
				fc_val = strchr(fc_line, '=');
				if (fc_val == NULL)
				{
					appendPQExpBuffer(fc_errorMessage,
									  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
									  fc_serviceFile,
									  fc_linenr);
					fc_result = 3;
					goto exit;
				}
				*fc_val++ = '\0';

				if (strcmp(fc_key, "service") == 0)
				{
					appendPQExpBuffer(fc_errorMessage,
									  libpq_gettext("nested service specifications not supported in service file \"%s\", line %d\n"),
									  fc_serviceFile,
									  fc_linenr);
					fc_result = 3;
					goto exit;
				}

				/*
				 * 设置参数 --- 但不要覆盖之前的任何
				 * 明确设置。
				 */
				fc_found_keyword = false;
				for (fc_i = 0; fc_options[fc_i].keyword; fc_i++)
				{
					if (strcmp(fc_options[fc_i].keyword, fc_key) == 0)
					{
						if (fc_options[fc_i].val == NULL)
							fc_options[fc_i].val = strdup(fc_val);
						if (!fc_options[fc_i].val)
						{
							appendPQExpBufferStr(fc_errorMessage,
												 libpq_gettext("out of memory\n"));
							fc_result = 3;
							goto exit;
						}
						fc_found_keyword = true;
						break;
					}
				}

				if (!fc_found_keyword)
				{
					appendPQExpBuffer(fc_errorMessage,
									  libpq_gettext("syntax error in service file \"%s\", line %d\n"),
									  fc_serviceFile,
									  fc_linenr);
					fc_result = 3;
					goto exit;
				}
			}
		}
	}

exit:
	fclose(fc_f);

	return fc_result;
}


/*
 *		PQconninfoParse
 *
 * 解析像PQconnectdb()那样的字符串，并返回
 * 结果连接选项数组。在失败时返回NULL。
 * 结果只包含在字符串中直接指定的选项，
 * 而不包含任何可能的默认值。
 *
 * 如果errmsg不是NULL，成功时*errmsg被设置为NULL，
 * 失败时设置为一个malloc'd字符串（使用PQfreemem释放它）。
 * 在内存不足的情况下，*errmsg和结果都可能为NULL。
 *
 * 注意：返回的数组是动态分配的，当不再需要时应
 * 通过PQconninfoFree()释放。
 */
PQconninfoOption * PQconninfoParse(const char *fc_conninfo, char **fc_errmsg)
{
	PQExpBufferData fc_errorBuf;
	PQconninfoOption *fc_connOptions;

	if (fc_errmsg)
		*fc_errmsg = NULL;			/* 默认 */
	initPQExpBuffer(&fc_errorBuf);
	if (PQExpBufferDataBroken(fc_errorBuf))
		return NULL;			/* 内存不足 :-( */
	fc_connOptions = fc_parse_connection_string(fc_conninfo, &fc_errorBuf, false);
	if (fc_connOptions == NULL && fc_errmsg)
		*fc_errmsg = fc_errorBuf.data;
	else
		termPQExpBuffer(&fc_errorBuf);
	return fc_connOptions;
}

/*
 * 构建常量PQconninfoOptions数组的工作副本。
 */
static PQconninfoOption * fc_conninfo_init(PQExpBuffer fc_errorMessage)
{
	PQconninfoOption *fc_options;
	PQconninfoOption *fc_opt_dest;
	const internalPQconninfoOption *fc_cur_opt;

	/*
	 * 为PQconninfoOptions中的所有选项分配足够的内存，即使有些
	 * 最终被过滤掉。
	 */
	fc_options = (PQconninfoOption *) malloc(sizeof(PQconninfoOption) * sizeof(PQconninfoOptions) / sizeof(PQconninfoOptions[0]));
	if (fc_options == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("out of memory\n"));
		return NULL;
	}
	fc_opt_dest = fc_options;

	for (fc_cur_opt = PQconninfoOptions; fc_cur_opt->keyword; fc_cur_opt++)
	{
		/* 仅复制结构的公共部分，而不是完整的内部部分 */
		memcpy(fc_opt_dest, fc_cur_opt, sizeof(PQconninfoOption));
		fc_opt_dest++;
	}
	MemSet(fc_opt_dest, 0, sizeof(PQconninfoOption));

	return fc_options;
}

/*
 * 连接字符串解析器
 *
 * 如果解析成功，则返回malloc'd PQconninfoOption数组。
 * 否则，返回NULL，并将错误信息添加到errorMessage中。
 *
 * 如果use_defaults为true，则填充默认值（来自服务文件，
 * 环境变量等）。
 */
static PQconninfoOption * fc_parse_connection_string(const char *fc_connstr, PQExpBuffer fc_errorMessage,
						bool fc_use_defaults)
{
	/* 如果连接字符串匹配URI前缀，则将其解析为URI */
	if (fc_uri_prefix_length(fc_connstr) != 0)
		return fc_conninfo_uri_parse(fc_connstr, fc_errorMessage, fc_use_defaults);

	/* 否则解析为默认值 */
	return fc_conninfo_parse(fc_connstr, fc_errorMessage, fc_use_defaults);
}

/*
 * 检查连接字符串是否以任何有效URI前缀
 * 指示符开头。
 *
 * 返回URI前缀长度，如果字符串不包含URI前缀，则返回0。
 *
 * XXX 这在psql/common.c中重复出现。
 */
static int fc_uri_prefix_length(const char *fc_connstr)
{
	if (strncmp(fc_connstr, uri_designator,
				sizeof(uri_designator) - 1) == 0)
		return sizeof(uri_designator) - 1;

	if (strncmp(fc_connstr, short_uri_designator,
				sizeof(short_uri_designator) - 1) == 0)
		return sizeof(short_uri_designator) - 1;

	return 0;
}

/*
 * 识别的连接字符串要么以有效的URI前缀开头，要么
 * 其中包含一个“=”.
 *
 * 必须与parse_connection_string一致：对于任何返回true的内容，
 * 至少看起来应该可以被该例程解析。
 *
 * XXX 这段内容在psql/common.c中重复出现
 */
static bool fc_recognized_connection_string(const char *fc_connstr)
{
	return fc_uri_prefix_length(fc_connstr) != 0 || strchr(fc_connstr, '=') != NULL;
}

/*
 * parse_connection_string的子例程
 *
 * 处理包含key=value对的字符串。
 */
static PQconninfoOption * fc_conninfo_parse(const char *fc_conninfo, PQExpBuffer fc_errorMessage,
			   bool fc_use_defaults)
{
	char	   *fc_pname;
	char	   *fc_pval;
	char	   *fc_buf;
	char	   *fc_cp;
	char	   *fc_cp2;
	PQconninfoOption *fc_options;

	/* 创建PQconninfoOptions的工作副本 */
	fc_options = fc_conninfo_init(fc_errorMessage);
	if (fc_options == NULL)
		return NULL;

	/* 需要一个可修改的输入字符串副本 */
	if ((fc_buf = strdup(fc_conninfo)) == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("out of memory\n"));
		PQconninfoFree(fc_options);
		return NULL;
	}
	fc_cp = fc_buf;

	while (*fc_cp)
	{
		/* 在参数名之前跳过空格 */
		if (isspace((unsigned char) *fc_cp))
		{
			fc_cp++;
			continue;
		}

		/* 获取参数名称 */
		fc_pname = fc_cp;
		while (*fc_cp)
		{
			if (*fc_cp == '=')
				break;
			if (isspace((unsigned char) *fc_cp))
			{
				*fc_cp++ = '\0';
				while (*fc_cp)
				{
					if (!isspace((unsigned char) *fc_cp))
						break;
					fc_cp++;
				}
				break;
			}
			fc_cp++;
		}

		/* 检查后面是否有“=” */
		if (*fc_cp != '=')
		{
			appendPQExpBuffer(fc_errorMessage,
							  libpq_gettext("missing \"=\" after \"%s\" in connection info string\n"),
							  fc_pname);
			PQconninfoFree(fc_options);
			free(fc_buf);
			return NULL;
		}
		*fc_cp++ = '\0';

		/* 在“=”后跳过空格 */
		while (*fc_cp)
		{
			if (!isspace((unsigned char) *fc_cp))
				break;
			fc_cp++;
		}

		/* 获取参数值 */
		fc_pval = fc_cp;

		if (*fc_cp != '\'')
		{
			fc_cp2 = fc_pval;
			while (*fc_cp)
			{
				if (isspace((unsigned char) *fc_cp))
				{
					*fc_cp++ = '\0';
					break;
				}
				if (*fc_cp == '\\')
				{
					fc_cp++;
					if (*fc_cp != '\0')
						*fc_cp2++ = *fc_cp++;
				}
				else
					*fc_cp2++ = *fc_cp++;
			}
			*fc_cp2 = '\0';
		}
		else
		{
			fc_cp2 = fc_pval;
			fc_cp++;
			for (;;)
			{
				if (*fc_cp == '\0')
				{
					appendPQExpBufferStr(fc_errorMessage,
										 libpq_gettext("unterminated quoted string in connection info string\n"));
					PQconninfoFree(fc_options);
					free(fc_buf);
					return NULL;
				}
				if (*fc_cp == '\\')
				{
					fc_cp++;
					if (*fc_cp != '\0')
						*fc_cp2++ = *fc_cp++;
					continue;
				}
				if (*fc_cp == '\'')
				{
					*fc_cp2 = '\0';
					fc_cp++;
					break;
				}
				*fc_cp2++ = *fc_cp++;
			}
		}

		/*
		 * 现在我们有了名称和值，存储记录。
		 */
		if (!fc_conninfo_storeval(fc_options, fc_pname, fc_pval, fc_errorMessage, false, false))
		{
			PQconninfoFree(fc_options);
			free(fc_buf);
			return NULL;
		}
	}

	/* 完成可修改的输入字符串 */
	free(fc_buf);

	/*
	 * 如果调用者希望，添加默认值。
	 */
	if (fc_use_defaults)
	{
		if (!fc_conninfo_add_defaults(fc_options, fc_errorMessage))
		{
			PQconninfoFree(fc_options);
			return NULL;
		}
	}

	return fc_options;
}

/*
 * Conninfo数组解析例程
 *
 * 如果成功，将返回一个malloc'd PQconninfoOption数组。
 * 如果不成功，则返回NULL，并将错误信息附加到errorMessage。
 * 默认值将被提供（来自服务文件、环境变量等）
 * 用于未指定的选项，但仅在use_defaults为true的情况下。
 *
 * 如果expand_dbname非零，并且传递的第一个“dbname”关键词的值
 * 是一个连接字符串（如recognized_connection_string所示），
 * 则解析并处理它，覆盖任何之前处理过的冲突关键词。
 * 随后的关键词将优先级更高。然而。在树内程序通常会指定expand_dbname=true，
 * 因此命令行参数命名数据库时可以使用连接字符串。
 * 一些代码从已知字面来源（如PQdb()、PQconninfoParse()和pg_database.datname）
 * 中获取任意数据库名称。当连接到这样的数据库时，树内代码首先将名称包装在连接字符串中。
 */
static PQconninfoOption * fc_conninfo_array_parse(const char *const *fc_keywords, const char *const *fc_values,
					 PQExpBuffer fc_errorMessage, bool fc_use_defaults,
					 int fc_expand_dbname)
{
	PQconninfoOption *fc_options;
	PQconninfoOption *fc_dbname_options = NULL;
	PQconninfoOption *fc_option;
	int			fc_i = 0;

	/*
	 * 如果expand_dbname非零，检查关键词“dbname”以查看值是否
	 * 实际上是一个已识别的连接字符串。
	 */
	while (fc_expand_dbname && fc_keywords[fc_i])
	{
		const char *fc_pname = fc_keywords[fc_i];
		const char *fc_pvalue = fc_values[fc_i];

		/* 首先找到“dbname”，如果有的话 */
		if (strcmp(fc_pname, "dbname") == 0 && fc_pvalue)
		{
			/*
			 * 如果值是一个连接字符串，解析它，但不要在这里使用
			 * 默认值——那些将在稍后选择。我们只想
			 * 覆盖实际传递的那些参数。
			 */
			if (fc_recognized_connection_string(fc_pvalue))
			{
				fc_dbname_options = fc_parse_connection_string(fc_pvalue, fc_errorMessage, false);
				if (fc_dbname_options == NULL)
					return NULL;
			}
			break;
		}
		++fc_i;
	}

	/* 创建PQconninfoOptions的工作副本 */
	fc_options = fc_conninfo_init(fc_errorMessage);
	if (fc_options == NULL)
	{
		PQconninfoFree(fc_dbname_options);
		return NULL;
	}

	/* 解析关键词/值数组 */
	fc_i = 0;
	while (fc_keywords[fc_i])
	{
		const char *fc_pname = fc_keywords[fc_i];
		const char *fc_pvalue = fc_values[fc_i];

		if (fc_pvalue != NULL && fc_pvalue[0] != '\0')
		{
			/* 搜索参数记录 */
			for (fc_option = fc_options; fc_option->keyword != NULL; fc_option++)
			{
				if (strcmp(fc_option->keyword, fc_pname) == 0)
					break;
			}

			/* 检查无效的连接选项 */
			if (fc_option->keyword == NULL)
			{
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("invalid connection option \"%s\"\n"),
								  fc_pname);
				PQconninfoFree(fc_options);
				PQconninfoFree(fc_dbname_options);
				return NULL;
			}

			/*
			 * 如果我们在第一个dbname参数上，并且我们有一个已解析的
			 * 连接字符串，则将这些参数复制过来，覆盖任何
			 * 现有的前一个设置。
			 */
			if (strcmp(fc_pname, "dbname") == 0 && fc_dbname_options)
			{
				PQconninfoOption *fc_str_option;

				for (fc_str_option = fc_dbname_options; fc_str_option->keyword != NULL; fc_str_option++)
				{
					if (fc_str_option->val != NULL)
					{
						int			fc_k;

						for (fc_k = 0; fc_options[fc_k].keyword; fc_k++)
						{
							if (strcmp(fc_options[fc_k].keyword, fc_str_option->keyword) == 0)
							{
								if (fc_options[fc_k].val)
									free(fc_options[fc_k].val);
								fc_options[fc_k].val = strdup(fc_str_option->val);
								if (!fc_options[fc_k].val)
								{
									appendPQExpBufferStr(fc_errorMessage,
														 libpq_gettext("out of memory\n"));
									PQconninfoFree(fc_options);
									PQconninfoFree(fc_dbname_options);
									return NULL;
								}
								break;
							}
						}
					}
				}

				/*
				 * 忘记解析的连接字符串，以便任何后续的
				 * dbname参数不会被扩展。
				 */
				PQconninfoFree(fc_dbname_options);
				fc_dbname_options = NULL;
			}
			else
			{
				/*
				 * 存储值，覆盖之前的设置
				 */
				if (fc_option->val)
					free(fc_option->val);
				fc_option->val = strdup(fc_pvalue);
				if (!fc_option->val)
				{
					appendPQExpBufferStr(fc_errorMessage,
										 libpq_gettext("out of memory\n"));
					PQconninfoFree(fc_options);
					PQconninfoFree(fc_dbname_options);
					return NULL;
				}
			}
		}
		++fc_i;
	}
	PQconninfoFree(fc_dbname_options);

	/*
	 * 如果调用者希望，添加默认值。
	 */
	if (fc_use_defaults)
	{
		if (!fc_conninfo_add_defaults(fc_options, fc_errorMessage))
		{
			PQconninfoFree(fc_options);
			return NULL;
		}
	}

	return fc_options;
}

/*
 * 将任何未指定选项的默认值添加到连接选项数组中。
 *
 * 默认值来自服务文件、环境变量等。
 *
 * 如果成功返回true，其他情况下返回false；如果提供了errorMessage，
 * 在失败时会填充此变量。请注意，未能找到默认值在这里不是错误条件---
 * 我们只是将选项的值保留为NULL。
 */
static bool fc_conninfo_add_defaults(PQconninfoOption *fc_options, PQExpBuffer fc_errorMessage)
{
	PQconninfoOption *fc_option;
	char	   *fc_tmp;

	/*
	 * 如果有服务规范，使用它获取任何未明确给出的参数。
	 * 如果未传递错误消息缓冲区，则忽略错误，因为没有办法传回失败消息。
	 */
	if (fc_parseServiceInfo(fc_options, fc_errorMessage) != 0 && fc_errorMessage)
		return false;

	/*
	 * 获取未在conninfo字符串或服务中指定的参数的后备资源。
	 */
	for (fc_option = fc_options; fc_option->keyword != NULL; fc_option++)
	{
		if (fc_option->val != NULL)
			continue;			/* 值在conninfo或服务中 */

		/*
		 * 尝试获取环境变量后备
		 */
		if (fc_option->envvar != NULL)
		{
			if ((fc_tmp = getenv(fc_option->envvar)) != NULL)
			{
				fc_option->val = strdup(fc_tmp);
				if (!fc_option->val)
				{
					if (fc_errorMessage)
						appendPQExpBufferStr(fc_errorMessage,
											 libpq_gettext("out of memory\n"));
					return false;
				}
				continue;
			}
		}

		/*
		 * 解释已弃用的PGREQUIRESSL环境变量。
		 * 根据传统，将以"1"开头的值转换为sslmode=require，
		 * 并忽略其他值。若同时设置PGREQUIRESSL=1和PGSSLMODE，
		 * 则PGSSLMODE优先；在v9.3之前则相反。
		 */
		if (strcmp(fc_option->keyword, "sslmode") == 0)
		{
			const char *fc_requiresslenv = getenv("PGREQUIRESSL");

			if (fc_requiresslenv != NULL && fc_requiresslenv[0] == '1')
			{
				fc_option->val = strdup("require");
				if (!fc_option->val)
				{
					if (fc_errorMessage)
						appendPQExpBufferStr(fc_errorMessage,
											 libpq_gettext("out of memory\n"));
					return false;
				}
				continue;
			}
		}

		/*
		 * 未指定环境变量或变量未设置 - 尝试使用编译默认值
		 */
		if (fc_option->compiled != NULL)
		{
			fc_option->val = strdup(fc_option->compiled);
			if (!fc_option->val)
			{
				if (fc_errorMessage)
					appendPQExpBufferStr(fc_errorMessage,
										 libpq_gettext("out of memory\n"));
				return false;
			}
			continue;
		}

		/*
		 * 对"user"选项的特殊处理。请注意，如果pg_fe_getauthname
		 * 失败，我们会将值保留为NULL；如果调用者提供用户名，
		 * 则没有必要将此视为错误条件。我们现在这样做的唯一原因
		 * 是确保PQconndefaults的调用者会看到正确的默认值
		 * （当然在没有错误的情况下）。
		 */
		if (strcmp(fc_option->keyword, "user") == 0)
		{
			fc_option->val = pg_fe_getauthname(NULL);
			continue;
		}
	}

	return true;
}

/*
 * parse_connection_string的子例程
 *
 * 处理URI连接字符串。
 */
static PQconninfoOption * fc_conninfo_uri_parse(const char *fc_uri, PQExpBuffer fc_errorMessage,
				   bool fc_use_defaults)
{
	PQconninfoOption *fc_options;

	/* 创建PQconninfoOptions的工作副本 */
	fc_options = fc_conninfo_init(fc_errorMessage);
	if (fc_options == NULL)
		return NULL;

	if (!fc_conninfo_uri_parse_options(fc_options, fc_uri, fc_errorMessage))
	{
		PQconninfoFree(fc_options);
		return NULL;
	}

	/*
	 * 如果调用者希望，添加默认值。
	 */
	if (fc_use_defaults)
	{
		if (!fc_conninfo_add_defaults(fc_options, fc_errorMessage))
		{
			PQconninfoFree(fc_options);
			return NULL;
		}
	}

	return fc_options;
}

/*
 * conninfo_uri_parse_options
 *		实际的URI解析器。
 *
 * 如果成功，返回true，同时选项数组被填充为来自URI的解析选项。
 * 如果不成功，返回false并根据情况填充errorMessage。
 *
 * 根据URI语法（RFC 3986）解析'uri'中的连接URI字符串：
 *
 * postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]
 *
 * 其中"netloc"是主机名、IPv4地址或被字面方括号包围的IPv6地址。
 * 作为扩展，我们还允许用逗号分隔多个netloc[:port]规格：
 *
 * postgresql://[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]
 *
 * URI的任何部分都可能使用百分比编码（%xy）。
 */
static bool fc_conninfo_uri_parse_options(PQconninfoOption *fc_options, const char *fc_uri,
						   PQExpBuffer fc_errorMessage)
{
	int			fc_prefix_len;
	char	   *fc_p;
	char	   *fc_buf = NULL;
	char	   *fc_start;
	char		fc_prevchar = '\0';
	char	   *fc_user = NULL;
	char	   *fc_host = NULL;
	bool		fc_retval = false;
	PQExpBufferData fc_hostbuf;
	PQExpBufferData fc_portbuf;

	initPQExpBuffer(&fc_hostbuf);
	initPQExpBuffer(&fc_portbuf);
	if (PQExpBufferDataBroken(fc_hostbuf) || PQExpBufferDataBroken(fc_portbuf))
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("out of memory\n"));
		goto cleanup;
	}

	/* 需要输入URI的可修改副本 */
	fc_buf = strdup(fc_uri);
	if (fc_buf == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage,
							 libpq_gettext("out of memory\n"));
		goto cleanup;
	}
	fc_start = fc_buf;

	/* 跳过URI前缀 */
	fc_prefix_len = fc_uri_prefix_length(fc_uri);
	if (fc_prefix_len == 0)
	{
		/* 不应发生 */
		appendPQExpBuffer(fc_errorMessage,
						  libpq_gettext("invalid URI propagated to internal parser routine: \"%s\"\n"),
						  fc_uri);
		goto cleanup;
	}
	fc_start += fc_prefix_len;
	fc_p = fc_start;

	/* 向前查看可能的用户凭证标识符 */
	while (*fc_p && *fc_p != '@' && *fc_p != '/')
		++fc_p;
	if (*fc_p == '@')
	{
		/*
		 * 找到用户名/密码标识符，因此URI应为"scheme://user[:password]@[netloc]"的形式。
		 */
		fc_user = fc_start;

		fc_p = fc_user;
		while (*fc_p != ':' && *fc_p != '@')
			++fc_p;

		/* 保存最后一个字符并在用户名结束时截断 */
		fc_prevchar = *fc_p;
		*fc_p = '\0';

		if (*fc_user &&
			!fc_conninfo_storeval(fc_options, "user", fc_user,
							   fc_errorMessage, false, true))
			goto cleanup;

		if (fc_prevchar == ':')
		{
			const char *fc_password = fc_p + 1;

			while (*fc_p != '@')
				++fc_p;
			*fc_p = '\0';

			if (*fc_password &&
				!fc_conninfo_storeval(fc_options, "password", fc_password,
								   fc_errorMessage, false, true))
				goto cleanup;
		}

		/* 解析的用户名或密码令牌的末尾向前推进 */
		++fc_p;
	}
	else
	{
		/*
		 * 没有找到用户名/密码指示符。重置到URI开始位置。
		 */
		fc_p = fc_start;
	}

	/*
	 * 可能有多个 netloc[:port] 对，它们之间用逗号分隔。
	 * 当我们最初进入这个循环时，“p”已经在此点向前推进了可选的URI凭据信息，
	 * 现在指向URI的“netloc”部分。在后续的循环迭代中，“p”
	 * 已经在逗号分隔符后向前推进，现在指向下一个“netloc”的开始。
	 */
	for (;;)
	{
		/*
		 * 寻找IPv6地址。
		 */
		if (*fc_p == '[')
		{
			fc_host = ++fc_p;
			while (*fc_p && *fc_p != ']')
				++fc_p;
			if (!*fc_p)
			{
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("end of string reached when looking for matching \"]\" in IPv6 host address in URI: \"%s\"\n"),
								  fc_uri);
				goto cleanup;
			}
			if (fc_p == fc_host)
			{
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("IPv6 host address may not be empty in URI: \"%s\"\n"),
								  fc_uri);
				goto cleanup;
			}

			/* 剪掉括号并向前推进 */
			*(fc_p++) = '\0';

			/*
			 * 地址后面可能跟着端口规范符、斜杠、查询或分隔逗号。
			 */
			if (*fc_p && *fc_p != ':' && *fc_p != '/' && *fc_p != '?' && *fc_p != ',')
			{
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("unexpected character \"%c\" at position %d in URI (expected \":\" or \"/\"): \"%s\"\n"),
								  *fc_p, (int) (fc_p - fc_buf + 1), fc_uri);
				goto cleanup;
			}
		}
		else
		{
			/* 不是IPv6地址：DNS命名或IPv4的netloc */
			fc_host = fc_p;

			/*
			 * 寻找端口规范符（冒号）或主机规范符的结束（斜杠）或查询（问号）或主机分隔符（逗号）。
			 */
			while (*fc_p && *fc_p != ':' && *fc_p != '/' && *fc_p != '?' && *fc_p != ',')
				++fc_p;
		}

		/* 在将主机名终止符置为null之前保存它 */
		fc_prevchar = *fc_p;
		*fc_p = '\0';

		appendPQExpBufferStr(&fc_hostbuf, fc_host);

		if (fc_prevchar == ':')
		{
			const char *fc_port = ++fc_p; /* 向前推进超过主机终止符 */

			while (*fc_p && *fc_p != '/' && *fc_p != '?' && *fc_p != ',')
				++fc_p;

			fc_prevchar = *fc_p;
			*fc_p = '\0';

			appendPQExpBufferStr(&fc_portbuf, fc_port);
		}

		if (fc_prevchar != ',')
			break;
		++fc_p;					/* 向前推进超过逗号分隔符 */
		appendPQExpBufferChar(&fc_hostbuf, ',');
		appendPQExpBufferChar(&fc_portbuf, ',');
	}

	/* 保存主机和端口的最终值。 */
	if (PQExpBufferDataBroken(fc_hostbuf) || PQExpBufferDataBroken(fc_portbuf))
		goto cleanup;
	if (fc_hostbuf.data[0] &&
		!fc_conninfo_storeval(fc_options, "host", fc_hostbuf.data,
						   fc_errorMessage, false, true))
		goto cleanup;
	if (fc_portbuf.data[0] &&
		!fc_conninfo_storeval(fc_options, "port", fc_portbuf.data,
						   fc_errorMessage, false, true))
		goto cleanup;

	if (fc_prevchar && fc_prevchar != '?')
	{
		const char *fc_dbname = ++fc_p;	/* 向前推进超过主机终止符 */

		/* 寻找查询参数 */
		while (*fc_p && *fc_p != '?')
			++fc_p;

		fc_prevchar = *fc_p;
		*fc_p = '\0';

		/*
		 * 避免将dbname设为空字符串，因为这会强制使用默认值（用户名）
		 * 并忽略$PGDATABASE，而不是完全不设置它。
		 */
		if (*fc_dbname &&
			!fc_conninfo_storeval(fc_options, "dbname", fc_dbname,
							   fc_errorMessage, false, true))
			goto cleanup;
	}

	if (fc_prevchar)
	{
		++fc_p;					/* 向前推进超过终止符 */

		if (!fc_conninfo_uri_parse_params(fc_p, fc_options, fc_errorMessage))
			goto cleanup;
	}

	/* 一切解析正常 */
	fc_retval = true;

cleanup:
	termPQExpBuffer(&fc_hostbuf);
	termPQExpBuffer(&fc_portbuf);
	if (fc_buf)
		free(fc_buf);
	return fc_retval;
}

/*
 * 连接URI参数解析例程
 *
 * 如果成功，则返回true，并且connOptions被填充解析参数。
 * 否则，返回false，并适当地填充errorMessage。
 *
 * 破坏性地修改'params'缓冲区。
 */
static bool fc_conninfo_uri_parse_params(char *fc_params,
						  PQconninfoOption *fc_connOptions,
						  PQExpBuffer fc_errorMessage)
{
	while (*fc_params)
	{
		char	   *fc_keyword = fc_params;
		char	   *fc_value = NULL;
		char	   *fc_p = fc_params;
		bool		fc_malloced = false;
		int			fc_oldmsglen;

		/*
		 * 扫描params字符串以查找'='和'&'，分别标记关键字和
		 * 值的结束。
		 */
		for (;;)
		{
			if (*fc_p == '=')
			{
				/* 是否已经有'='？ */
				if (fc_value != NULL)
				{
					appendPQExpBuffer(fc_errorMessage,
									  libpq_gettext("extra key/value separator \"=\" in URI query parameter: \"%s\"\n"),
									  fc_keyword);
					return false;
				}
				/* 剪掉关键字，向前推进到值 */
				*fc_p++ = '\0';
				fc_value = fc_p;
			}
			else if (*fc_p == '&' || *fc_p == '\0')
			{
				/*
				 * 如果未到达末尾，剪掉值并向前推进；如果有，
				 * 让p指向下一个参数的开始。
				 */
				if (*fc_p != '\0')
					*fc_p++ = '\0';
				/* 真的有'='吗？ */
				if (fc_value == NULL)
				{
					appendPQExpBuffer(fc_errorMessage,
									  libpq_gettext("missing key/value separator \"=\" in URI query parameter: \"%s\"\n"),
									  fc_keyword);
					return false;
				}
				/* 得到关键字和值，去处理它们。 */
				break;
			}
			else
				++fc_p;			/* 在所有其他字节上推进。 */
		}

		fc_keyword = fc_conninfo_uri_decode(fc_keyword, fc_errorMessage);
		if (fc_keyword == NULL)
		{
			/* conninfo_uri_decode已经设置了错误信息 */
			return false;
		}
		fc_value = fc_conninfo_uri_decode(fc_value, fc_errorMessage);
		if (fc_value == NULL)
		{
			/* conninfo_uri_decode已经设置了错误信息 */
			free(fc_keyword);
			return false;
		}
		fc_malloced = true;

		/*
		 * 针对改进的JDBC兼容性进行特殊关键字处理。
		 */
		if (strcmp(fc_keyword, "ssl") == 0 &&
			strcmp(fc_value, "true") == 0)
		{
			free(fc_keyword);
			free(fc_value);
			fc_malloced = false;

			fc_keyword = "sslmode";
			fc_value = "require";
		}

		/*
		 * 如果存在相应的选项，则存储值；否则忽略。
		 * 此时，关键字和值都没有进行URI编码。
		 */
		fc_oldmsglen = fc_errorMessage->len;
		if (!fc_conninfo_storeval(fc_connOptions, fc_keyword, fc_value,
							   fc_errorMessage, true, false))
		{
			/* 如果conninfo_storeval没有提供通用消息，则插入通用消息。 */
			if (fc_errorMessage->len == fc_oldmsglen)
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("invalid URI query parameter: \"%s\"\n"),
								  fc_keyword);
			/* 失败。 */
			if (fc_malloced)
			{
				free(fc_keyword);
				free(fc_value);
			}
			return false;
		}

		if (fc_malloced)
		{
			free(fc_keyword);
			free(fc_value);
		}

		/* 继续处理下一个key=value对，如果有的话 */
		fc_params = fc_p;
	}

	return true;
}

/*
 * 连接 URI 解码例程
 *
 * 如果成功，返回分配的解码字符串。
 * 如果不成功，返回 NULL，并相应地填充 errorMessage。
 *
 * 字符串通过用相应字符替换任何百分比编码的标记来解码，同时保留任何未编码的字符。 百分比编码的标记是一个字符三元组：一个百分号，后跟一对十六进制数字（0-9A-F），其中小写和大写字母被视为相同。
 */
static char * fc_conninfo_uri_decode(const char *fc_str, PQExpBuffer fc_errorMessage)
{
	char	   *fc_buf;
	char	   *fc_p;
	const char *fc_q = fc_str;

	fc_buf = malloc(strlen(fc_str) + 1);
	if (fc_buf == NULL)
	{
		appendPQExpBufferStr(fc_errorMessage, libpq_gettext("out of memory\n"));
		return NULL;
	}
	fc_p = fc_buf;

	for (;;)
	{
		if (*fc_q != '%')
		{
			/* 复制并检查 NUL 终止符 */
			if (!(*(fc_p++) = *(fc_q++)))
				break;
		}
		else
		{
			int			fc_hi;
			int			fc_lo;
			int			fc_c;

			++fc_q;				/* 跳过百分号本身 */

			/*
			 * 第一次调用 get_hexdigit() 将捕获可能的行尾，因此我们永远不会解除引用无效的 q 指针。
			 */
			if (!(fc_get_hexdigit(*fc_q++, &fc_hi) && fc_get_hexdigit(*fc_q++, &fc_lo)))
			{
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("invalid percent-encoded token: \"%s\"\n"),
								  fc_str);
				free(fc_buf);
				return NULL;
			}

			fc_c = (fc_hi << 4) | fc_lo;
			if (fc_c == 0)
			{
				appendPQExpBuffer(fc_errorMessage,
								  libpq_gettext("forbidden value %%00 in percent-encoded value: \"%s\"\n"),
								  fc_str);
				free(fc_buf);
				return NULL;
			}
			*(fc_p++) = fc_c;
		}
	}

	return fc_buf;
}

/*
 * 将十六进制数字字符转换为其整数值。
 *
 * 如果成功，返回 true，并且值被填充为数字的基数 16 值。
 * 如果不成功，返回 false。
 *
 * 范围 A-F 内的小写和大写字母被视为相同。
 */
static bool fc_get_hexdigit(char fc_digit, int *fc_value)
{
	if ('0' <= fc_digit && fc_digit <= '9')
		*fc_value = fc_digit - '0';
	else if ('A' <= fc_digit && fc_digit <= 'F')
		*fc_value = fc_digit - 'A' + 10;
	else if ('a' <= fc_digit && fc_digit <= 'f')
		*fc_value = fc_digit - 'a' + 10;
	else
		return false;

	return true;
}

/*
 * 找到与 connOptions 数组中的关键字相对应的选项值。
 *
 * 如果成功，返回指向相应选项值的指针。
 * 如果不成功，返回 NULL。
 */
static const char * fc_conninfo_getval(PQconninfoOption *fc_connOptions,
				const char *fc_keyword)
{
	PQconninfoOption *fc_option;

	fc_option = fc_conninfo_find(fc_connOptions, fc_keyword);

	return fc_option ? fc_option->val : NULL;
}

/*
 * 存储与 connOptions 数组中的关键字相对应的选项的新值。
 *
 * 如果 uri_decode 为真，则值被 URI 解码。关键字始终假定为未 URI 编码。
 *
 * 如果成功，返回指向相应 PQconninfoOption 的指针，其值被替换为传递的值字符串的 strdup 副本。
 * 如果有现有值，将在替换之前释放该选项的现有值。
 *
 * 如果不成功，返回 NULL，并相应地填充 errorMessage。
 * 但是，如果失败的原因是传递的关键字无效，并且 ignoreMissing 为真，则 errorMessage 将保持不变。
 */
static PQconninfoOption * fc_conninfo_storeval(PQconninfoOption *fc_connOptions,
				  const char *fc_keyword, const char *fc_value,
				  PQExpBuffer fc_errorMessage, bool fc_ignoreMissing,
				  bool fc_uri_decode)
{
	PQconninfoOption *fc_option;
	char	   *fc_value_copy;

	/*
	 * 为了向后兼容，requiressl=1 被翻译为 sslmode=require，requiressl=0 被翻译为 sslmode=prefer
	 * （这是 sslmode 的默认值）。
	 */
	if (strcmp(fc_keyword, "requiressl") == 0)
	{
		fc_keyword = "sslmode";
		if (fc_value[0] == '1')
			fc_value = "require";
		else
			fc_value = "prefer";
	}

	fc_option = fc_conninfo_find(fc_connOptions, fc_keyword);
	if (fc_option == NULL)
	{
		if (!fc_ignoreMissing)
			appendPQExpBuffer(fc_errorMessage,
							  libpq_gettext("invalid connection option \"%s\"\n"),
							  fc_keyword);
		return NULL;
	}

	if (fc_uri_decode)
	{
		fc_value_copy = fc_conninfo_uri_decode(fc_value, fc_errorMessage);
		if (fc_value_copy == NULL)
			/* conninfo_uri_decode已经设置了错误信息 */
			return NULL;
	}
	else
	{
		fc_value_copy = strdup(fc_value);
		if (fc_value_copy == NULL)
		{
			appendPQExpBufferStr(fc_errorMessage, libpq_gettext("out of memory\n"));
			return NULL;
		}
	}

	if (fc_option->val)
		free(fc_option->val);
	fc_option->val = fc_value_copy;

	return fc_option;
}

/*
 * 找到与 connOptions 数组中的关键字相对应的 PQconninfoOption 选项。
 *
 * 如果成功，返回指向相应 PQconninfoOption 结构的指针。
 * 如果不成功，返回 NULL。
 */
static PQconninfoOption * fc_conninfo_find(PQconninfoOption *fc_connOptions, const char *fc_keyword)
{
	PQconninfoOption *fc_option;

	for (fc_option = fc_connOptions; fc_option->keyword != NULL; fc_option++)
	{
		if (strcmp(fc_option->keyword, fc_keyword) == 0)
			return fc_option;
	}

	return NULL;
}


/*
 * 返回用于连接的连接选项
 */
PQconninfoOption * PQconninfo(PGconn *fc_conn)
{
	PQExpBufferData fc_errorBuf;
	PQconninfoOption *fc_connOptions;

	if (fc_conn == NULL)
		return NULL;

	/*
	 * 我们在这里实际上不会报告任何错误，但被调用者需要一个缓冲区，
	 * 而且我们更喜欢不要破坏连接的 errorMessage。
	 */
	initPQExpBuffer(&fc_errorBuf);
	if (PQExpBufferDataBroken(fc_errorBuf))
		return NULL;			/* 内存不足 :-( */

	fc_connOptions = fc_conninfo_init(&fc_errorBuf);

	if (fc_connOptions != NULL)
	{
		const internalPQconninfoOption *fc_option;

		for (fc_option = PQconninfoOptions; fc_option->keyword; fc_option++)
		{
			char	  **fc_connmember;

			if (fc_option->connofs < 0)
				continue;

			fc_connmember = (char **) ((char *) fc_conn + fc_option->connofs);

			if (*fc_connmember)
				fc_conninfo_storeval(fc_connOptions, fc_option->keyword, *fc_connmember,
								  &fc_errorBuf, true, false);
		}
	}

	termPQExpBuffer(&fc_errorBuf);

	return fc_connOptions;
}


void PQconninfoFree(PQconninfoOption *fc_connOptions)
{
	PQconninfoOption *fc_option;

	if (fc_connOptions == NULL)
		return;

	for (fc_option = fc_connOptions; fc_option->keyword != NULL; fc_option++)
	{
		if (fc_option->val != NULL)
			free(fc_option->val);
	}
	free(fc_connOptions);
}


/* =========== PGconn 的访问器函数 ========= */
char * PQdb(const PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;
	return fc_conn->dbName;
}

char * PQuser(const PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;
	return fc_conn->pguser;
}

char * PQpass(const PGconn *fc_conn)
{
	char	   *fc_password = NULL;

	if (!fc_conn)
		return NULL;
	if (fc_conn->connhost != NULL)
		fc_password = fc_conn->connhost[fc_conn->whichhost].password;
	if (fc_password == NULL)
		fc_password = fc_conn->pgpass;
	/* 历史上我们返回 "" 而不是 NULL 表示未指定密码 */
	if (fc_password == NULL)
		fc_password = "";
	return fc_password;
}

char * PQhost(const PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;

	if (fc_conn->connhost != NULL)
	{
		/*
		 * 返回用户提供的原始主机值，或在其缺失时返回 hostaddr。
		 */
		if (fc_conn->connhost[fc_conn->whichhost].host != NULL &&
			fc_conn->connhost[fc_conn->whichhost].host[0] != '\0')
			return fc_conn->connhost[fc_conn->whichhost].host;
		else if (fc_conn->connhost[fc_conn->whichhost].hostaddr != NULL &&
				 fc_conn->connhost[fc_conn->whichhost].hostaddr[0] != '\0')
			return fc_conn->connhost[fc_conn->whichhost].hostaddr;
	}

	return "";
}

char * PQhostaddr(const PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;

	/* 返回解析后的 IP 地址 */
	if (fc_conn->connhost != NULL && fc_conn->connip != NULL)
		return fc_conn->connip;

	return "";
}

char * PQport(const PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;

	if (fc_conn->connhost != NULL)
		return fc_conn->connhost[fc_conn->whichhost].port;

	return "";
}

/*
 * 不再执行任何操作，但该函数保留以便于 API 向后兼容。
 */
char * PQtty(const PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;
	return "";
}

char * PQoptions(const PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;
	return fc_conn->pgoptions;
}

ConnStatusType
PQstatus(const PGconn *fc_conn)
{
	if (!fc_conn)
		return CONNECTION_BAD;
	return fc_conn->status;
}

PGTransactionStatusType
PQtransactionStatus(const PGconn *fc_conn)
{
	if (!fc_conn || fc_conn->status != CONNECTION_OK)
		return PQTRANS_UNKNOWN;
	if (fc_conn->asyncStatus != PGASYNC_IDLE)
		return PQTRANS_ACTIVE;
	return fc_conn->xactStatus;
}

const char * PQparameterStatus(const PGconn *fc_conn, const char *fc_paramName)
{
	const pgParameterStatus *fc_pstatus;

	if (!fc_conn || !fc_paramName)
		return NULL;
	for (fc_pstatus = fc_conn->pstatus; fc_pstatus != NULL; fc_pstatus = fc_pstatus->next)
	{
		if (strcmp(fc_pstatus->name, fc_paramName) == 0)
			return fc_pstatus->value;
	}
	return NULL;
}

int PQprotocolVersion(const PGconn *fc_conn)
{
	if (!fc_conn)
		return 0;
	if (fc_conn->status == CONNECTION_BAD)
		return 0;
	return PG_PROTOCOL_MAJOR(fc_conn->pversion);
}

int PQserverVersion(const PGconn *fc_conn)
{
	if (!fc_conn)
		return 0;
	if (fc_conn->status == CONNECTION_BAD)
		return 0;
	return fc_conn->sversion;
}

char * PQerrorMessage(const PGconn *fc_conn)
{
	if (!fc_conn)
		return libpq_gettext("connection pointer is NULL\n");

	/*
	 * errorMessage 缓冲区可能因先前未能为消息分配足够的内存而标记为“损坏”。在这种情况下，告诉应用程序我们内存不足。
	 */
	if (PQExpBufferBroken(&fc_conn->errorMessage))
		return libpq_gettext("out of memory\n");

	return fc_conn->errorMessage.data;
}

/*
 * 在 Windows 中，套接字值是无符号的，非法的套接字值 (INVALID_SOCKET) 是 ~0，这在比较中等于 -1（没有编译器警告）。理想情况下，我们希望在 Windows 上为 PQsocket() 返回一个无符号值，但这将导致该函数的返回值与 Unix 不同，因此我们只返回 -1 代表非法套接字。
 * http://msdn.microsoft.com/en-us/library/windows/desktop/cc507522%28v=vs.85%29.aspx
 * http://stackoverflow.com/questions/10817252/why-is-invalid-socket-defined-as-0-in-winsock2-h-c
 */
int PQsocket(const PGconn *fc_conn)
{
	if (!fc_conn)
		return -1;
	return (fc_conn->sock != PGINVALID_SOCKET) ? fc_conn->sock : -1;
}

int PQbackendPID(const PGconn *fc_conn)
{
	if (!fc_conn || fc_conn->status != CONNECTION_OK)
		return 0;
	return fc_conn->be_pid;
}

PGpipelineStatus
PQpipelineStatus(const PGconn *fc_conn)
{
	if (!fc_conn)
		return PQ_PIPELINE_OFF;

	return fc_conn->pipelineStatus;
}

int PQconnectionNeedsPassword(const PGconn *fc_conn)
{
	char	   *fc_password;

	if (!fc_conn)
		return false;
	fc_password = PQpass(fc_conn);
	if (fc_conn->password_needed &&
		(fc_password == NULL || fc_password[0] == '\0'))
		return true;
	else
		return false;
}

int PQconnectionUsedPassword(const PGconn *fc_conn)
{
	if (!fc_conn)
		return false;
	if (fc_conn->password_needed)
		return true;
	else
		return false;
}

int PQclientEncoding(const PGconn *fc_conn)
{
	if (!fc_conn || fc_conn->status != CONNECTION_OK)
		return -1;
	return fc_conn->client_encoding;
}

int PQsetClientEncoding(PGconn *fc_conn, const char *fc_encoding)
{
	char		fc_qbuf[128];
	static const char fc_query[] = "set client_encoding to '%s'";
	PGresult   *fc_res;
	int			fc_status;

	if (!fc_conn || fc_conn->status != CONNECTION_OK)
		return -1;

	if (!fc_encoding)
		return -1;

	/* 解析来自区域设置的特殊“自动”值 */
	if (strcmp(fc_encoding, "auto") == 0)
		fc_encoding = pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true));

	/* 检查查询缓冲区溢出 */
	if (sizeof(fc_qbuf) < (sizeof(fc_query) + strlen(fc_encoding)))
		return -1;

	/* 好的，现在发送查询 */
	sprintf(fc_qbuf, fc_query, fc_encoding);
	fc_res = PQexec(fc_conn, fc_qbuf);

	if (fc_res == NULL)
		return -1;
	if (fc_res->resultStatus != PGRES_COMMAND_OK)
		fc_status = -1;
	else
	{
		/*
		 * 我们依赖后端报告参数值，我们将在那时更改状态。
		 */
		fc_status = 0;				/* 一切正常 */
	}
	PQclear(fc_res);
	return fc_status;
}

PGVerbosity
PQsetErrorVerbosity(PGconn *fc_conn, PGVerbosity fc_verbosity)
{
	PGVerbosity fc_old;

	if (!fc_conn)
		return PQERRORS_DEFAULT;
	fc_old = fc_conn->verbosity;
	fc_conn->verbosity = fc_verbosity;
	return fc_old;
}

PGContextVisibility
PQsetErrorContextVisibility(PGconn *fc_conn, PGContextVisibility fc_show_context)
{
	PGContextVisibility fc_old;

	if (!fc_conn)
		return PQSHOW_CONTEXT_ERRORS;
	fc_old = fc_conn->show_context;
	fc_conn->show_context = fc_show_context;
	return fc_old;
}

PQnoticeReceiver
PQsetNoticeReceiver(PGconn *fc_conn, PQnoticeReceiver fc_proc, void *fc_arg)
{
	PQnoticeReceiver fc_old;

	if (fc_conn == NULL)
		return NULL;

	fc_old = fc_conn->noticeHooks.noticeRec;
	if (fc_proc)
	{
		fc_conn->noticeHooks.noticeRec = fc_proc;
		fc_conn->noticeHooks.noticeRecArg = fc_arg;
	}
	return fc_old;
}

PQnoticeProcessor
PQsetNoticeProcessor(PGconn *fc_conn, PQnoticeProcessor fc_proc, void *fc_arg)
{
	PQnoticeProcessor fc_old;

	if (fc_conn == NULL)
		return NULL;

	fc_old = fc_conn->noticeHooks.noticeProc;
	if (fc_proc)
	{
		fc_conn->noticeHooks.noticeProc = fc_proc;
		fc_conn->noticeHooks.noticeProcArg = fc_arg;
	}
	return fc_old;
}

/*
 * 默认通知消息接收器只是获取标准通知文本，并将其发送到通知处理器。这个两级设置主要是为了向后兼容；也许我们应该弃用 PQsetNoticeProcessor 的使用？
 */
static void fc_defaultNoticeReceiver(void *fc_arg, const PGresult *fc_res)
{
	(void) fc_arg;					/* 未使用 */
	if (fc_res->noticeHooks.noticeProc != NULL)
		fc_res->noticeHooks.noticeProc(fc_res->noticeHooks.noticeProcArg,
									PQresultErrorMessage(fc_res));
}

/*
 * 默认通知消息处理器仅在 stderr 上打印消息。如果应用程序希望将消息发送到其他地方（例如窗口），可以覆盖此行为。请注意，仅仅丢弃通知可能是个坏主意。
 */
static void fc_defaultNoticeProcessor(void *fc_arg, const char *fc_message)
{
	(void) fc_arg;					/* 未使用 */
	/* 注意：我们期望提供的字符串已经以换行符结尾。 */
	fprintf(stderr, "%s", fc_message);
}

/*
 * 返回指向下一个标记的指针，如果当前标记不匹配，则返回 NULL
 */
static char * fc_pwdfMatchesString(char *fc_buf, const char *fc_token)
{
	char	   *fc_tbuf;
	const char *fc_ttok;
	bool		fc_bslash = false;

	if (fc_buf == NULL || fc_token == NULL)
		return NULL;
	fc_tbuf = fc_buf;
	fc_ttok = fc_token;
	if (fc_tbuf[0] == '*' && fc_tbuf[1] == ':')
		return fc_tbuf + 2;
	while (*fc_tbuf != 0)
	{
		if (*fc_tbuf == '\\' && !fc_bslash)
		{
			fc_tbuf++;
			fc_bslash = true;
		}
		if (*fc_tbuf == ':' && *fc_ttok == 0 && !fc_bslash)
			return fc_tbuf + 1;
		fc_bslash = false;
		if (*fc_ttok == 0)
			return NULL;
		if (*fc_tbuf == *fc_ttok)
		{
			fc_tbuf++;
			fc_ttok++;
		}
		else
			return NULL;
	}
	return NULL;
}

/* 从密码文件中获取密码。返回值是 malloc 分配的。 */
static char * fc_passwordFromFile(const char *fc_hostname, const char *fc_port, const char *fc_dbname,
				 const char *fc_username, const char *fc_pgpassfile)
{
	FILE	   *fc_fp;
	struct stat fc_stat_buf;
	PQExpBufferData fc_buf;

	if (fc_dbname == NULL || fc_dbname[0] == '\0')
		return NULL;

	if (fc_username == NULL || fc_username[0] == '\0')
		return NULL;

	/* 'localhost' 匹配 '' 的 pghost 或默认套接字目录 */
	if (fc_hostname == NULL || fc_hostname[0] == '\0')
		fc_hostname = DefaultHost;
	else if (is_unixsock_path(fc_hostname))

		/*
		 * 我们可能应该使用 canonicalize_path()，但那样我们就必须将 path.c 放入 libpq 中，似乎不值得。
		 */
		if (strcmp(fc_hostname, DEFAULT_PGSOCKET_DIR) == 0)
			fc_hostname = DefaultHost;

	if (fc_port == NULL || fc_port[0] == '\0')
		fc_port = DEF_PGPORT_STR;

	/* 如果无法打开密码文件，则忽略它。 */
	if (stat(fc_pgpassfile, &fc_stat_buf) != 0)
		return NULL;

#ifndef WIN32
	if (!S_ISREG(fc_stat_buf.st_mode))
	{
		fprintf(stderr,
				libpq_gettext("WARNING: password file \"%s\" is not a plain file\n"),
				fc_pgpassfile);
		return NULL;
	}

	/* 如果密码文件不安全，提醒用户并忽略它。 */
	if (fc_stat_buf.st_mode & (S_IRWXG | S_IRWXO))
	{
		fprintf(stderr,
				libpq_gettext("WARNING: password file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
				fc_pgpassfile);
		return NULL;
	}
#else

	/*
	 * 在 Win32 上，目录是受保护的，因此我们无需检查文件。
	 */
#endif

	fc_fp = fopen(fc_pgpassfile, "r");
	if (fc_fp == NULL)
		return NULL;

	/* 使用可扩展缓冲区以适应任何合理的行长度 */
	initPQExpBuffer(&fc_buf);

	while (!feof(fc_fp) && !ferror(fc_fp))
	{
		/* 确保缓冲区中有合理的空间 */
		if (!enlargePQExpBuffer(&fc_buf, 128))
			break;

		/* 读取一些数据，并将其附加到我们已有的内容上 */
		if (fgets(fc_buf.data + fc_buf.len, fc_buf.maxlen - fc_buf.len, fc_fp) == NULL)
			break;
		fc_buf.len += strlen(fc_buf.data + fc_buf.len);

		/* 如果我们还没有整行，循环读取更多内容 */
		if (!(fc_buf.len > 0 && fc_buf.data[fc_buf.len - 1] == '\n') && !feof(fc_fp))
			continue;

		/* 忽略注释 */
		if (fc_buf.data[0] != '#')
		{
			char	   *fc_t = fc_buf.data;
			int			fc_len;

			/* 去掉尾随的换行符和回车 */
			fc_len = pg_strip_crlf(fc_t);

			if (fc_len > 0 &&
				(fc_t = fc_pwdfMatchesString(fc_t, fc_hostname)) != NULL &&
				(fc_t = fc_pwdfMatchesString(fc_t, fc_port)) != NULL &&
				(fc_t = fc_pwdfMatchesString(fc_t, fc_dbname)) != NULL &&
				(fc_t = fc_pwdfMatchesString(fc_t, fc_username)) != NULL)
			{
				/* 找到匹配。 */
				char	   *fc_ret,
						   *fc_p1,
						   *fc_p2;

				fc_ret = strdup(fc_t);

				fclose(fc_fp);
				explicit_bzero(fc_buf.data, fc_buf.maxlen);
				termPQExpBuffer(&fc_buf);

				if (!fc_ret)
				{
					/* 内存不足。XXX： error 信息会很好。 */
					return NULL;
				}

				/* 解码密码。 */
				for (fc_p1 = fc_p2 = fc_ret; *fc_p1 != ':' && *fc_p1 != '\0'; ++fc_p1, ++fc_p2)
				{
					if (*fc_p1 == '\\' && fc_p1[1] != '\0')
						++fc_p1;
					*fc_p2 = *fc_p1;
				}
				*fc_p2 = '\0';

				return fc_ret;
			}
		}

		/* 没有匹配，重置缓冲区以准备下一行。 */
		fc_buf.len = 0;
	}

	fclose(fc_fp);
	explicit_bzero(fc_buf.data, fc_buf.maxlen);
	termPQExpBuffer(&fc_buf);
	return NULL;
}


/*
 * 如果连接由于错误的密码而失败，我们应该提到我们是否从 pgpassfile 获取了密码。
 */
static void fc_pgpassfileWarning(PGconn *fc_conn)
{
	/* 如果是“无效授权”，添加 pgpassfile 提及 */
	/* 仅适用于 >= 9.0 的服务器 */
	if (fc_conn->password_needed &&
		fc_conn->connhost[fc_conn->whichhost].password != NULL &&
		fc_conn->result)
	{
		const char *fc_sqlstate = PQresultErrorField(fc_conn->result,
												  PG_DIAG_SQLSTATE);

		if (fc_sqlstate && strcmp(fc_sqlstate, ERRCODE_INVALID_PASSWORD) == 0)
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("password retrieved from file \"%s\"\n"),
							  fc_conn->pgpassfile);
	}
}

/*
 * 检查输入的 SSL 协议值是否有效。
 * 这作为连接参数 ssl_min_protocol_version 和 ssl_max_protocol_version 的有效性检查例程。
 */
static bool fc_sslVerifyProtocolVersion(const char *fc_version)
{
	/*
	 * 空字符串和 NULL 值被视为有效，因为它们
	 * 相当于忽略该参数。
	 */
	if (!fc_version || strlen(fc_version) == 0)
		return true;

	if (pg_strcasecmp(fc_version, "TLSv1") == 0 ||
		pg_strcasecmp(fc_version, "TLSv1.1") == 0 ||
		pg_strcasecmp(fc_version, "TLSv1.2") == 0 ||
		pg_strcasecmp(fc_version, "TLSv1.3") == 0)
		return true;

	/* 其他任何情况都是错误的 */
	return false;
}


/*
 * 确保输入的 SSL 协议范围是正确的。检查
 * 在输入字符串上执行，以保持与 TLS 后端无关。对该函数的输入
 * 预期已通过 sslVerifyProtocolVersion() 进行验证。
 */
static bool fc_sslVerifyProtocolRange(const char *fc_min, const char *fc_max)
{
	Assert(fc_sslVerifyProtocolVersion(fc_min) &&
		   fc_sslVerifyProtocolVersion(fc_max));

	/* 如果至少一个边界未设置，则范围是有效的 */
	if (fc_min == NULL || fc_max == NULL || strlen(fc_min) == 0 || strlen(fc_max) == 0)
		return true;

	/*
	 * 如果最小版本是我们接受的最低版本，则最大版本
	 * 的所有选项都是有效的。
	 */
	if (pg_strcasecmp(fc_min, "TLSv1") == 0)
		return true;

	/*
	 * 最小边界是有效的，并且不能是 TLSv1，因此将
	 * TLSv1 用作最大值是不正确的。
	 */
	if (pg_strcasecmp(fc_max, "TLSv1") == 0)
		return false;

	/*
	 * 此时我们知道我们有一个 TLSv1.1 到 1.3
	 * 版本的混合。
	 */
	if (pg_strcasecmp(fc_min, fc_max) > 0)
		return false;

	return true;
}


/*
 * 获取用户的主目录，返回到给定的缓冲区
 *
 * 在 Unix 系统上，这实际上返回用户的主目录。在 Windows 上
 * 返回的是 PostgreSQL 特定的应用程序数据文件夹。
 *
 * 这基本上与 get_home_path() 相同，但我们不使用它
 * 因为我们不想将 path.c 引入 libpq（它会污染应用程序
 * 命名空间）。
 *
 * 成功时返回真，获取目录名称失败时返回假。
 *
 * 小心：尽管在大多数情况下，失败是意外的，但有些用户
 * 喜欢在没有主目录的环境中运行应用程序。在失败时，
 * 你几乎肯定不想报告错误。只需表现得好像你希望在
 * 主目录中找到的任何文件不在那里（实际上它不在）。
 */
bool pqGetHomeDirectory(char *fc_buf, int fc_bufsize)
{
#ifndef WIN32
	const char *fc_home;

	fc_home = getenv("HOME");
	if (fc_home == NULL || fc_home[0] == '\0')
		return pg_get_user_home_dir(geteuid(), fc_buf, fc_bufsize);
	strlcpy(fc_buf, fc_home, fc_bufsize);
	return true;
#else
	char		tmppath[MAX_PATH];

	ZeroMemory(tmppath, sizeof(tmppath));
	if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, tmppath) != S_OK)
		return false;
	snprintf(buf, bufsize, "%s/postgresql", tmppath);
	return true;
#endif
}

/*
 * 为了保持 API 一致性，总是提供锁定存根，即使
 * 它们不是必需的。
 *
 * 由于我们在 pgthreadlock_t API 中忽略了提供任何错误返回约定，
 * 除了在任何互斥体原语失败时进行 Assert，我们无法做太多。
 * 幸运的是，此类失败在实际应用中似乎不存在。
 */

static void fc_default_threadlock(int fc_acquire)
{
#ifdef ENABLE_THREAD_SAFETY
	static pthread_mutex_t fc_singlethread_lock = PTHREAD_MUTEX_INITIALIZER;

	if (fc_acquire)
	{
		if (pthread_mutex_lock(&fc_singlethread_lock))
			Assert(false);
	}
	else
	{
		if (pthread_mutex_unlock(&fc_singlethread_lock))
			Assert(false);
	}
#endif
}

pgthreadlock_t
PQregisterThreadLock(pgthreadlock_t fc_newhandler)
{
	pgthreadlock_t fc_prev = pg_g_threadlock;

	if (fc_newhandler)
		pg_g_threadlock = fc_newhandler;
	else
		pg_g_threadlock = fc_default_threadlock;

	return fc_prev;
}
